diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md deleted file mode 100755 index 3683b0f3e..000000000 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: 提交问题 -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) 搜索答案。 -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: - - 数据库: - - APIJSON: - -**问题描述** - -可以输入具体的步骤,代码信息,或者截图,能帮助我们更快的解决您的问题 - -**错误信息** - -运行日志面板错误信息,错误截图,或者【帮助>切换开发者工具】日志,以及【帮助>查看运行日志】log.log 文件 - diff --git a/.github/ISSUE_TEMPLATE/--custom.md b/.github/ISSUE_TEMPLATE/--custom.md deleted file mode 100755 index 5a47bb25e..000000000 --- a/.github/ISSUE_TEMPLATE/--custom.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: 其它反馈 -about: Describe this issue template's purpose here. - ---- - - diff --git a/.github/ISSUE_TEMPLATE/--feature_request.md b/.github/ISSUE_TEMPLATE/--feature_request.md deleted file mode 100755 index f8c0c4c7d..000000000 --- a/.github/ISSUE_TEMPLATE/--feature_request.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: 功能改进 -about: Suggest an idea for this project - ---- - -**具体说下** -希望改进...希望做成...希望新增... - -**为什么** -说明它在哪些情况下会带来哪些效果。 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..f0d9515f5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,83 @@ +name: Bug Report/报告 bug +description: "Create a report to help us improve, please read FAQ first./帮助我们更好地改进项目,但请先阅读常见问题与提问前必看,不要提已有的重复问题!" +title: "[Bug] " +labels: [kind/bug] +body: +- type: markdown + attributes: + value: "如果你已经知道问题所在、怎么解决,请直接 提交 Pull Request 为社区做贡献,非常感谢。\n开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲会友等,也有心情不好和身体病痛,\n往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~\n少数个人的热情终有被耗尽的一天,只有大家共同建设和繁荣社区,才能让开源可持续发展! " + +- type: input + attributes: + label: APIJSON Version/APIJSON 版本号 + placeholder: | + e.g./例如 5.4.0 ,如果不是最新版请用最新版,复现问题再来,原则上不更新旧版,而是只维护一个最新版 + + validations: + required: true + +- type: input + attributes: + label: Database Type & Version/数据库类型及版本号 + placeholder: | + e.g./例如 MySQL 5.7.34 + + validations: + required: true + +- type: textarea + attributes: + label: Environment/环境信息 + description: | + e.g./例如: + - **JDK/基础库**: 1.8.0_17 + - **OS/系统**: MacOS Monterey 12.6 (21G115) M1 + value: | + - JDK/基础库: + - OS/系统: + render: markdown + + validations: + required: true + +- type: input + attributes: + label: APIAuto Screenshots/APIAuto 请求与结果完整截屏 + description: "Upload by copy and paste image file or url./复制图片文件或 URL 再粘贴到输入框(用 APIAuto 能静态检查出很多问题,甚至还有修复建议,不用浪费你我的时间)\n https://github.com/TommyLemon/APIAuto " + value: + + validations: + required: true + +- type: textarea + attributes: + label: Current Behavior/问题描述 + description: "A concise description of what you're experiencing. Must contains screenshots./\n\n**提 bug 请发请求和响应的【完整截屏】,没图的自行解决!\n开发者有限的时间和精力主要放在【维护项目源码和文档】上!\n【描述不详细】 或 【文档/常见问题 已有答案】 的问题可能会被忽略!!\n【态度 不文明/不友善】的可能会被拉黑,问题也可能不予解答!!!**\n\n请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,\n大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 设计规范 来调用 API \n https://github.com/Tencent/APIJSON/issues/181 " + render: markdown + + validations: + required: true + +- type: textarea + attributes: + label: Expected Behavior/期望结果 + description: A concise description of what you expected to happen./具体描述你期望返回什么样的结果或者达到什么样的效果? + render: markdown + + validations: + required: false + + +- type: textarea + attributes: + label: Any additional comments?/其它补充说明? + description: | + e.g. some background/context of how you ran into this bug./例如:一些背景或上下文信息,包括复现步骤、相关日志等 + render: markdown + + validations: + required: false + +- type: markdown + attributes: + value: "Please follow the rules to fulfil all required inputs. You can add screenshots by comment after submit this issue./\n请按要求填写所有必填项,未填完将提交不了!\n如果随意填写敷衍了事,将直接关闭 issue,问题不会得到解答!\n可以提交后再通过回复评论来补充上传截屏图片(复制粘贴文件)。\n如果是网页 bug 等与你无关的原因导致提交不了,可以改为填问卷:\n https://wj.qq.com/s2/10971431/2a09 " diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..3ba13e0ce --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..6f14d47b3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,17 @@ +name: Feature Request/期望新增功能 +description: Request a new feature/期望新增什么样的功能或特性,或者做哪些方面的改进? +title: "[Feature] " +labels: [kind/feature] +body: +- type: textarea + attributes: + label: Description + description: | + Please describe what this feature does./具体描述下是什么样的功能或特性,以及你为什么想要它,用在什么场景,碰到了什么痛点,有什么解决思路,尝试过哪些,效果怎样? + + validations: + required: true + +- type: markdown + attributes: + value: 推荐去建议收集箱提问,也方便 统一检索和管理、投票决定优先级、更新处理进度 等: https://github.com/Tencent/APIJSON/issues/37 diff --git a/.github/ISSUE_TEMPLATE/other_issues.yml b/.github/ISSUE_TEMPLATE/other_issues.yml new file mode 100644 index 000000000..9f9a6d0ea --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other_issues.yml @@ -0,0 +1,16 @@ +name: Other Issues/其它反馈 +description: For questions, suggestions, improvements and others./问题(非 bug)、建议(非新增功能) 或 其它 +title: "[xxx] " +body: +- type: textarea + attributes: + label: Description + description: | + Please describe the issue./请具体描述,包括是什么、为什么、如何做 + + validations: + required: true + +- type: markdown + attributes: + value: "Bug 反馈请使用正确的模板,用错模板将直接关闭 issue,不予解答:\n https://github.com/Tencent/APIJSON/issues/new?assignees=&labels=kind%2Fbug&template=bug_report.yml&title=%5BBug%5D+ \n有建议请去建议收集箱提问,也方便 统一检索和管理、投票决定优先级、更新处理进度 等:\n https://github.com/Tencent/APIJSON/issues/37 " diff --git a/.gitignore b/.gitignore index 204504dc5..a06bbd0a8 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ build/ ### VS Code ### .vscode/ +APIJSONORM/bin +*.DS_Store diff --git a/APIJSONORM/README.md b/APIJSONORM/README.md index 7e0850527..8007c02c3 100644 --- a/APIJSONORM/README.md +++ b/APIJSONORM/README.md @@ -25,7 +25,6 @@ Tencent [APIJSON](https://github.com/Tencent/APIJSON) ORM library for remote dep ``` -


@@ -49,3 +48,18 @@ Tencent [APIJSON](https://github.com/Tencent/APIJSON) ORM library for remote dep implementation 'com.github.Tencent:APIJSON:latest' } ``` + +
+
+ +### FASTJSON 2 +#### Code +https://github.com/Tencent/APIJSON/tree/fastjson2 + +#### Maven +https://mvnrepository.com/artifact/com.github.linushp/zikai-apijson/1.0 + +
+ +### Unit Test +http://apijson.cn/unit diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml old mode 100755 new mode 100644 index 3f776fb6a..2d0768fff --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -3,9 +3,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - apijson.orm - apijson-orm - 5.1.0-F2 + com.github.Tencent + APIJSON + 7.8.0 jar APIJSONORM @@ -15,18 +15,16 @@ UTF-8 UTF-8 1.8 + UTF-8 + 1.8 + 1.8 com.alibaba fastjson - 2.0.4 - - - javax.activation - activation - 1.1.1 + 1.2.83 @@ -35,11 +33,26 @@ org.apache.maven.plugins maven-compiler-plugin + 3.12.1 1.8 1.8 + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + package + + jar-no-fork + + + + diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index b610cfc5b..d7854aae1 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,10 +64,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()); } @@ -97,11 +97,11 @@ public static JSONObject parseObject(String json) { * @return */ public static T parseObject(String json, Class clazz) { - if (clazz == null) { - Log.e(TAG, "parseObject clazz == null >> return null;"); + if (clazz == null || StringUtil.isEmpty(json, true)) { + Log.e(TAG, "parseObject clazz == null || StringUtil.isEmpty(json, true) >> 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()); } @@ -131,10 +131,14 @@ public static JSONArray parseArray(Object obj) { * @return */ public static JSONArray parseArray(String json) { - try { - return com.alibaba.fastjson.JSON.parseArray(getCorrectJson(json, true)); - } catch (Exception e) { - Log.i(TAG, "parseArray catch \n" + e.getMessage()); + if (StringUtil.isEmpty(json, true)) { + Log.e(TAG, "parseArray StringUtil.isEmpty(json, true) >> return null;"); + } else { + try { + return com.alibaba.fastjson.JSON.parseArray(getCorrectJson(json, true)); + } catch (Exception e) { + Log.i(TAG, "parseArray catch \n" + e.getMessage()); + } } return null; } @@ -152,8 +156,8 @@ public static List parseArray(JSONArray array, Class clazz) { * @return */ public static List parseArray(String json, Class clazz) { - if (clazz == null) { - Log.e(TAG, "parseArray clazz == null >> return null;"); + if (clazz == null || StringUtil.isEmpty(json, true)) { + Log.e(TAG, "parseArray clazz == null || StringUtil.isEmpty(json, true) >> return null;"); } else { try { return com.alibaba.fastjson.JSON.parseArray(getCorrectJson(json, true), clazz); @@ -169,6 +173,9 @@ public static List parseArray(String json, Class clazz) { * @return */ public static String toJSONString(Object obj) { + if (obj == null) { + return null; + } if (obj instanceof String) { return (String) obj; } @@ -186,13 +193,16 @@ public static String toJSONString(Object obj) { * @return */ public static String toJSONString(Object obj, SerializerFeature... features) { + if (obj == null) { + return null; + } if (obj instanceof String) { return (String) obj; } try { return com.alibaba.fastjson.JSON.toJSONString(obj, features); } catch (Exception e) { - Log.e(TAG, "parseArray catch \n" + e.getMessage()); + Log.e(TAG, "toJSONString catch \n" + e.getMessage()); } return null; } diff --git a/APIJSONORM/src/main/java/apijson/JSONObject.java b/APIJSONORM/src/main/java/apijson/JSONObject.java index 6825a02de..6f4359019 100755 --- a/APIJSONORM/src/main/java/apijson/JSONObject.java +++ b/APIJSONORM/src/main/java/apijson/JSONObject.java @@ -6,6 +6,7 @@ package apijson; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -138,8 +139,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_NAMESPACE = "@namespace"; //命名空间,Table 在非默认 namespace 内时需要声明 + public static final String KEY_CATALOG = "@catalog"; //目录,Table 在非默认 catalog 内时需要声明 + public static final String KEY_SCHEMA = "@schema"; //数据库,Table 在非默认 schema 内时需要声明 public static final String KEY_EXPLAIN = "@explain"; //分析 true/false public static final String KEY_CACHE = "@cache"; //缓存 RAM/ROM/ALL public static final String KEY_COLUMN = "@column"; //查询的Table字段或SQL函数 @@ -149,16 +152,29 @@ public JSONObject setUserIdIn(List list) { 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_KEY = "@key"; // key 映射,year:left(date,4);name_tag:(name,tag) public static final String KEY_RAW = "@raw"; // 自定义原始 SQL 片段 public static final String KEY_JSON = "@json"; //SQL Server 把字段转为 JSON 输出 + public static final String KEY_METHOD = "@method"; // json 对象配置操作方法 + public static final String KEY_GET = "@get"; // json 对象配置操作方法 + public static final String KEY_GETS = "@gets"; // json 对象配置操作方法 + public static final String KEY_HEAD = "@head"; // json 对象配置操作方法 + public static final String KEY_HEADS = "@heads"; // json 对象配置操作方法 + public static final String KEY_POST = "@post"; // json 对象配置操作方法 + public static final String KEY_PUT = "@put"; // json 对象配置操作方法 + public static final String KEY_DELETE = "@delete"; // json 对象配置操作方法 + + public static final Map KEY_METHOD_ENUM_MAP; public static final List TABLE_KEY_LIST; static { 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_NAMESPACE); + TABLE_KEY_LIST.add(KEY_CATALOG); + TABLE_KEY_LIST.add(KEY_SCHEMA); TABLE_KEY_LIST.add(KEY_EXPLAIN); TABLE_KEY_LIST.add(KEY_CACHE); TABLE_KEY_LIST.add(KEY_COLUMN); @@ -170,8 +186,26 @@ public JSONObject setUserIdIn(List list) { TABLE_KEY_LIST.add(KEY_HAVING); TABLE_KEY_LIST.add(KEY_HAVING_AND); TABLE_KEY_LIST.add(KEY_ORDER); + TABLE_KEY_LIST.add(KEY_KEY); TABLE_KEY_LIST.add(KEY_RAW); TABLE_KEY_LIST.add(KEY_JSON); + TABLE_KEY_LIST.add(KEY_METHOD); + TABLE_KEY_LIST.add(KEY_GET); + TABLE_KEY_LIST.add(KEY_GETS); + TABLE_KEY_LIST.add(KEY_HEAD); + TABLE_KEY_LIST.add(KEY_HEADS); + TABLE_KEY_LIST.add(KEY_POST); + TABLE_KEY_LIST.add(KEY_PUT); + TABLE_KEY_LIST.add(KEY_DELETE); + + KEY_METHOD_ENUM_MAP = new LinkedHashMap<>(); + KEY_METHOD_ENUM_MAP.put(KEY_GET, RequestMethod.GET); + KEY_METHOD_ENUM_MAP.put(KEY_GETS, RequestMethod.GETS); + KEY_METHOD_ENUM_MAP.put(KEY_HEAD, RequestMethod.HEAD); + KEY_METHOD_ENUM_MAP.put(KEY_HEADS, RequestMethod.HEADS); + KEY_METHOD_ENUM_MAP.put(KEY_POST, RequestMethod.POST); + KEY_METHOD_ENUM_MAP.put(KEY_PUT, RequestMethod.PUT); + KEY_METHOD_ENUM_MAP.put(KEY_DELETE, RequestMethod.DELETE); } //@key关键字都放这个类 >>>>>>>>>>>>>>>>>>>>>> @@ -223,13 +257,6 @@ 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 @@ -237,6 +264,27 @@ public JSONObject setSchema(String schema) { public JSONObject setDatasource(String datasource) { return puts(KEY_DATASOURCE, datasource); } + /**set namespace where table was puts + * @param namespace + * @return this + */ + public JSONObject setNamespace(String namespace) { + return puts(KEY_NAMESPACE, namespace); + } + /**set catalog where table was puts + * @param catalog + * @return this + */ + public JSONObject setCatalog(String catalog) { + return puts(KEY_CATALOG, catalog); + } + /**set schema where table was puts + * @param schema + * @return this + */ + public JSONObject setSchema(String schema) { + return puts(KEY_SCHEMA, schema); + } /**set if return explain informations * @param explain * @return @@ -377,6 +425,14 @@ public JSONObject setOrder(String keys) { return puts(KEY_ORDER, keys); } + /**set key map + * @param keyMap "name_tag:(name,tag);year:left(date,1,5)..." + * @return + */ + public JSONObject setKey(String keyMap) { + return puts(KEY_KEY, keyMap); + } + /**set keys to raw * @param keys "key0,key1,key2..." * @return @@ -393,18 +449,6 @@ public JSONObject setJson(String keys) { return puts(KEY_JSON, keys); } - /**用 setJson 替代。 - * set keys to cast to json - * @param keys "key0,key1,key2..." - * @return - * @see #{@link #setJson(String)} - */ - @Deprecated - public JSONObject setJSON(String keys) { - return puts(KEY_JSON, keys); - } - - //JSONObject内关键词 key >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -454,6 +498,31 @@ public JSONObject putsEmpty(String key, boolean isEmpty, boolean trim) { public JSONObject putsLength(String key, String compare) { return puts(key+"{}", SQL.length(key) + compare); } + /** + * @param key + * @param compare <=, > ... + * @param value 1, 5, 3.14, -99 ... + * @return {@link #puts(String, Object)} + */ + public JSONObject putsLength(String key, String compare, Object value) { + return puts(key+"["+(StringUtil.isEmpty(compare) || "=".equals(compare) ? "" : ("!=".equals(compare) ? "!" : compare)), value); + } + /** + * @param key + * @param compare <=0, >5 ... + * @return {@link #puts(String, Object)} + */ + public JSONObject putsJSONLength(String key, String compare) { + return puts(key+"{}", SQL.json_length(key) + compare); + } + /** + * @param key + * @param compare <=0, >5 ... + * @return {@link #puts(String, Object)} + */ + public JSONObject putsJSONLength(String key, String compare, Object value) { + return puts(key + "{" + (StringUtil.isEmpty(compare) || "=".equals(compare) ? "" : ("!=".equals(compare) ? "!" : compare)), value); + } /**设置搜索 * type = SEARCH_TYPE_CONTAIN_FULL diff --git a/APIJSONORM/src/main/java/apijson/JSONRequest.java b/APIJSONORM/src/main/java/apijson/JSONRequest.java index 8707cc2c0..62d724199 100755 --- a/APIJSONORM/src/main/java/apijson/JSONRequest.java +++ b/APIJSONORM/src/main/java/apijson/JSONRequest.java @@ -9,6 +9,8 @@ import java.util.List; import java.util.Map; +import static apijson.StringUtil.PATTERN_ALPHA_BIG; + /**wrapper for request * @author Lemon * @see #puts @@ -150,7 +152,7 @@ public JSONRequest setSubqueryRange(String range) { } /**set from for Subquery - * @param range + * @param from * @return */ public JSONRequest setSubqueryFrom(String from) { @@ -196,4 +198,69 @@ public JSONRequest puts(String key, Object value) { return this; } + + /**ABCdEfg => upper ? A-B-CD-EFG : a-b-cd-efg + * @param key + * @return + */ + public static String recoverHyphen(@NotNull String key, Boolean upper) { + return recoverDivider(key, "-", upper); + } + + /**ABCdEfg => upper ? A_B_CD_EFG : a_b_cd_efg + * @param key + * @return + */ + public static String recoverUnderline(@NotNull String key, Boolean upper) { + return recoverDivider(key, "_", upper); + } + + /**ABCdEfg => upper ? A$B$CD$EFG : a$b$cd$efg + * @param key + * @return + */ + public static String recoverDollar(@NotNull String key, Boolean upper) { + return recoverDivider(key, "$", upper); + } + + /**ABCdEfg => upper ? A.B.CD.EFG : a.b.cd.efg + * @param key + * @return + */ + public static String recoverDot(@NotNull String key, Boolean upper) { + return recoverDivider(key, ".", upper); + } + + /**ABCdEfg => upper ? A_B_CD_EFG : a/b/cd/efg + * @param key + * @return + */ + public static String recoverDivider(@NotNull String key, Boolean upper) { + return recoverDivider(key, "/", upper); + } + + /**驼峰格式转为带分隔符的全大写或全小写格式 + * @param key + * @param divider + * @param upper + * @return + */ + public static String recoverDivider(@NotNull String key, @NotNull String divider, Boolean upper) { + StringBuilder name = new StringBuilder(); + char[] cs = key.toCharArray(); + int len = key.length(); + for (int i = 0; i < len; i++) { + String s = key.substring(i, i + 1); + if (i > 0 && PATTERN_ALPHA_BIG.matcher(s).matches()) { + name.append(divider); + } + if (upper != null) { + s = upper ? s.toUpperCase() : s.toLowerCase(); + } + name.append(s); + } + return name.toString(); + } + + } diff --git a/APIJSONORM/src/main/java/apijson/JSONResponse.java b/APIJSONORM/src/main/java/apijson/JSONResponse.java index c69955c3f..21f3fe8f6 100755 --- a/APIJSONORM/src/main/java/apijson/JSONResponse.java +++ b/APIJSONORM/src/main/java/apijson/JSONResponse.java @@ -5,13 +5,12 @@ package apijson; -import java.util.List; -import java.util.Set; -import java.util.StringTokenizer; - import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import java.util.List; +import java.util.Set; + /**parser for response * @author Lemon * @see #getObject @@ -23,6 +22,17 @@ public class JSONResponse extends apijson.JSONObject { private static final long serialVersionUID = 1L; + // 节约性能和减少 bug,除了关键词 @key ,一般都符合变量命名规范,不符合也原样返回便于调试 + /**格式化带 - 中横线的单词 + */ + public static boolean IS_FORMAT_HYPHEN = false; + /**格式化带 _ 下划线的单词 + */ + public static boolean IS_FORMAT_UNDERLINE = false; + /**格式化带 $ 美元符的单词 + */ + public static boolean IS_FORMAT_DOLLAR = false; + private static final String TAG = "JSONResponse"; public JSONResponse() { @@ -206,10 +216,6 @@ public JSONResponse getJSONResponse(String key) { //状态信息,非GET请求获得的信息>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - /** * key = clazz.getSimpleName() * @param clazz @@ -408,18 +414,18 @@ public static String getTableName(String fullName) { /**获取变量名 * @param fullName - * @return {@link #formatKey(String, boolean, boolean, boolean, boolean)} formatColon = true, formatAt = true, formatHyphen = true, firstCase = true + * @return {@link #formatKey(String, boolean, boolean, boolean, boolean, boolean, boolean)} formatColon = true, formatAt = true, formatHyphen = true, firstCase = true */ public static String getVariableName(String fullName) { if (isArrayKey(fullName)) { fullName = StringUtil.addSuffix(fullName.substring(0, fullName.length() - 2), "list"); } - return formatKey(fullName, true, true, true, true); + return formatKey(fullName, true, true, true, true, false, true); } /**格式化数组的名称 key[] => keyList; key:alias[] => aliasList; Table-column[] => tableColumnList * @param key empty ? "list" : key + "List" 且首字母小写 - * @return {@link #formatKey(String, boolean, boolean, boolean, boolean)} formatColon = false, formatAt = true, formatHyphen = true, firstCase = true + * @return {@link #formatKey(String, boolean, boolean, boolean, boolean, boolean, boolean)} formatColon = false, formatAt = true, formatHyphen = true, firstCase = true */ public static String formatArrayKey(String key) { if (isArrayKey(key)) { @@ -430,28 +436,29 @@ public static String formatArrayKey(String key) { return key.substring(index + 1); //不处理自定义的 } - return formatKey(key, false, true, true, true); //节约性能,除了数组对象 Table-column:alias[] ,一般都符合变量命名规范 + return formatKey(key, false, true, true, IS_FORMAT_UNDERLINE, IS_FORMAT_DOLLAR, false); //节约性能,除了数组对象 Table-column:alias[] ,一般都符合变量命名规范 } /**格式化对象的名称 name => name; name:alias => alias * @param key name 或 name:alias - * @return {@link #formatKey(String, boolean, boolean, boolean, boolean)} formatColon = false, formatAt = true, formatHyphen = false, firstCase = true + * @return {@link #formatKey(String, boolean, boolean, boolean, boolean, boolean, boolean)} formatColon = false, formatAt = true, formatHyphen = false, firstCase = true */ public static String formatObjectKey(String key) { int index = key == null ? -1 : key.indexOf(":"); if (index >= 0) { - return key.substring(index + 1); //不处理自定义的 + return key.substring(index + 1); // 不处理自定义的 } - return formatKey(key, false, true, false, true); //节约性能,除了表对象 Table:alias ,一般都符合变量命名规范 + return formatKey(key, false, true, IS_FORMAT_HYPHEN, IS_FORMAT_UNDERLINE, IS_FORMAT_DOLLAR, false); //节约性能,除了表对象 Table:alias ,一般都符合变量命名规范 } /**格式化普通值的名称 name => name; name:alias => alias * @param fullName name 或 name:alias - * @return {@link #formatKey(String, boolean, boolean, boolean, boolean)} formatColon = false, formatAt = true, formatHyphen = false, firstCase = false + * @return {@link #formatKey(String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean)} formatColon = false, formatAt = true, formatHyphen = false, firstCase = false */ public static String formatOtherKey(String fullName) { - return formatKey(fullName, false, true, false, false); //节约性能,除了关键词 @key ,一般都符合变量命名规范,不符合也原样返回便于调试 + return formatKey(fullName, false, true, IS_FORMAT_HYPHEN, IS_FORMAT_UNDERLINE, IS_FORMAT_DOLLAR + , IS_FORMAT_HYPHEN || IS_FORMAT_UNDERLINE || IS_FORMAT_DOLLAR ? false : null); } @@ -460,10 +467,13 @@ public static String formatOtherKey(String fullName) { * @param formatAt 去除前缀 @ , @a => a * @param formatColon 去除分隔符 : , A:b => b * @param formatHyphen 去除分隔符 - , A-b-cd-Efg => aBCdEfg + * @param formatUnderline 去除分隔符 _ , A_b_cd_Efg => aBCdEfg + * @param formatDollar 去除分隔符 $ , A$b$cd$Efg => aBCdEfg * @param firstCase 第一个单词首字母小写,后面的首字母大写, Ab => ab ; A-b-Cd => aBCd * @return name => name; name:alias => alias */ - public static String formatKey(String fullName, boolean formatColon, boolean formatAt, boolean formatHyphen, boolean firstCase) { + public static String formatKey(String fullName, boolean formatColon, boolean formatAt, boolean formatHyphen + , boolean formatUnderline, boolean formatDollar, Boolean firstCase) { if (fullName == null) { Log.w(TAG, "formatKey fullName == null >> return null;"); return null; @@ -475,11 +485,18 @@ public static String formatKey(String fullName, boolean formatColon, boolean for if (formatAt) { //关键词只去掉前缀,不格式化单词,例如 @a-b 返回 a-b ,最后不会调用 setter fullName = formatAt(fullName); } - if (formatHyphen) { - fullName = formatHyphen(fullName, firstCase); + if (formatHyphen && fullName.contains("-")) { + fullName = formatHyphen(fullName, true); + } + if (formatUnderline && fullName.contains("_")) { + fullName = formatUnderline(fullName, true); + } + if (formatDollar && fullName.contains("$")) { + fullName = formatDollar(fullName, true); } - return firstCase ? StringUtil.firstCase(fullName) : fullName; //不格式化普通 key:value (value 不为 [], {}) 的 key + // 默认不格式化普通 key:value (value 不为 [], {}) 的 key + return firstCase == null ? fullName : StringUtil.firstCase(fullName, firstCase); } /**"@key" => "key" @@ -489,6 +506,7 @@ public static String formatKey(String fullName, boolean formatColon, boolean for public static String formatAt(@NotNull String key) { return key.startsWith("@") ? key.substring(1) : key; } + /**key:alias => alias * @param key * @return @@ -502,15 +520,148 @@ public static String formatColon(@NotNull String key) { * @param key * @return */ - public static String formatHyphen(@NotNull String key, boolean firstCase) { - String name = ""; + public static String formatHyphen(@NotNull String key) { + return StringUtil.firstCase(formatHyphen(key, true), false); + } + /**A-b-cd-Efg => ABCdEfg + * @param key + * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 + * @return + */ + public static String formatHyphen(@NotNull String key, Boolean firstCase) { + return formatHyphen(key, firstCase, false); + } + /**A-b-cd-Efg => ABCdEfg + * @param key + * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 + * @param otherCase 非首字符的大小写,true-大写,false-小写,null-不处理 + * @return + */ + public static String formatHyphen(@NotNull String key, Boolean firstCase, Boolean otherCase) { + return formatDivider(key, "-", firstCase, otherCase); + } + + /**A_b_cd_Efg => ABCdEfg + * @param key + * @return + */ + public static String formatUnderline(@NotNull String key) { + return StringUtil.firstCase(formatUnderline(key, true), false); + } + /**A_b_cd_Efg => ABCdEfg + * @param key + * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 + * @return + */ + public static String formatUnderline(@NotNull String key, Boolean firstCase) { + return formatUnderline(key, firstCase, false); + } + /**A_b_cd_Efg => ABCdEfg + * @param key + * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 + * @param otherCase 非首字符的大小写,true-大写,false-小写,null-不处理 + * @return + */ + public static String formatUnderline(@NotNull String key, Boolean firstCase, Boolean otherCase) { + return formatDivider(key, "_", firstCase, otherCase); + } + + /**A$b$cd$Efg => ABCdEfg + * @param key + * @return + */ + public static String formatDollar(@NotNull String key) { + return StringUtil.firstCase(formatDollar(key, true), false); + } + /**A$b$cd$Efg => ABCdEfg + * @param key + * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 + * @return + */ + public static String formatDollar(@NotNull String key, Boolean firstCase) { + return formatDollar(key, firstCase, false); + } + /**A$b$cd$Efg => ABCdEfg + * @param key + * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 + * @param otherCase 非首字符的大小写,true-大写,false-小写,null-不处理 + * @return + */ + public static String formatDollar(@NotNull String key, Boolean firstCase, Boolean otherCase) { + return formatDivider(key, "$", firstCase, otherCase); + } + + /**A.b.cd.Efg => ABCdEfg + * @param key + * @return + */ + public static String formatDot(@NotNull String key) { + return StringUtil.firstCase(formatDot(key, true), false); + } + /**A.b.cd.Efg => ABCdEfg + * @param key + * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 + * @return + */ + public static String formatDot(@NotNull String key, Boolean firstCase) { + return formatDot(key, firstCase, false); + } + /**A.b.cd.Efg => ABCdEfg + * @param key + * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 + * @param otherCase 非首字符的大小写,true-大写,false-小写,null-不处理 + * @return + */ + public static String formatDot(@NotNull String key, Boolean firstCase, Boolean otherCase) { + return formatDivider(key, ".", firstCase, otherCase); + } + + /**A/b/cd/Efg => ABCdEfg + * @param key + * @return + */ + public static String formatDivider(@NotNull String key, Boolean firstCase) { + return formatDivider(key, "/", firstCase); + } + + /**去除分割符,返回驼峰格式 + * @param key + * @param divider + * @return + */ + public static String formatDivider(@NotNull String key, @NotNull String divider) { + return StringUtil.firstCase(formatDivider(key, divider, true), false); + } + /**去除分割符,返回驼峰格式 + * @param key + * @param divider + * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 + * @return + */ + public static String formatDivider(@NotNull String key, @NotNull String divider, Boolean firstCase) { + return formatDivider(key, divider, firstCase, false); + } - StringTokenizer parts = new StringTokenizer(key, "-"); - name += parts.nextToken(); - while(parts.hasMoreTokens()) { - String part = parts.nextToken(); - name += firstCase ? StringUtil.firstCase(part, true) : part; + /**去除分割符,返回驼峰格式 + * @param key + * @param divider + * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 + * @param otherCase 非首字符的大小写,true-大写,false-小写,null-不处理 + * @return + */ + public static String formatDivider(@NotNull String key, @NotNull String divider, Boolean firstCase, Boolean otherCase) { + String[] parts = StringUtil.split(key, divider); + StringBuilder name = new StringBuilder(); + for (String part : parts) { + if (otherCase != null) { + part = otherCase ? part.toUpperCase() : part.toLowerCase(); + } + if (firstCase != null) { + part = StringUtil.firstCase(part, firstCase); + } + name.append(part); } - return name; + return name.toString(); } + } diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index f3328c768..86c2e61cc 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -13,19 +13,31 @@ public class Log { public static boolean DEBUG = true; - - public static final String VERSION = "5.1.0"; - public static final String KEY_SYSTEM_INFO_DIVIDER = "---|-----APIJSON SYSTEM INFO-----|---"; + + public static final String VERSION = "7.8.0"; + public static final String KEY_SYSTEM_INFO_DIVIDER = "\n---|-----APIJSON SYSTEM INFO-----|---\n"; + + public static final String OS_NAME; + public static final String OS_VERSION; + public static final String OS_ARCH; + public static final String JAVA_VERSION; + static { + OS_NAME = System.getProperty("os.name"); + OS_VERSION = System.getProperty("os.version"); + OS_ARCH = System.getProperty("os.arch"); + JAVA_VERSION = System.getProperty("java.version"); + } + //默认的时间格式 - public static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); + public static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); /** * modify date format * @param dateFormatString */ public static void setDateFormat(String dateFormatString) { - dateFormat = new SimpleDateFormat(dateFormatString); + DATE_FORMAT = new SimpleDateFormat(dateFormatString); } /** @@ -36,10 +48,10 @@ public static void setDateFormat(String dateFormatString) { */ 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); + System.err.println(DATE_FORMAT.format(System.currentTimeMillis()) + ": " + TAG + "." + level + ": " + msg); } else if(level.equals("VERBOSE") || level .equals("INFO") ){ - System.out.println(dateFormat.format(System.currentTimeMillis()) + ": " + TAG + "." + level + ": " + msg); + System.out.println(DATE_FORMAT.format(System.currentTimeMillis()) + ": " + TAG + "." + level + ": " + msg); } } diff --git a/APIJSONORM/src/main/java/apijson/RequestMethod.java b/APIJSONORM/src/main/java/apijson/RequestMethod.java index caca99225..875200b7a 100755 --- a/APIJSONORM/src/main/java/apijson/RequestMethod.java +++ b/APIJSONORM/src/main/java/apijson/RequestMethod.java @@ -5,6 +5,9 @@ package apijson; +import java.util.Arrays; +import java.util.List; + /**请求方法,对应org.springframework.web.bind.annotation.RequestMethod,多出GETS,HEADS方法 * @author Lemon */ @@ -43,10 +46,18 @@ public enum RequestMethod { /** * 删除数据 */ - DELETE; - - public static final RequestMethod[] ALL = new RequestMethod[]{ GET, HEAD, GETS, HEADS, POST, PUT, DELETE}; + DELETE, + + /** + * json 包含多条语句,支持增删改查、函数调用 + */ + CRUD; + public static final RequestMethod[] ALL = new RequestMethod[]{ GET, HEAD, GETS, HEADS, POST, PUT, DELETE, CRUD }; + public static final List ALL_NAME_LIST = Arrays.asList( + GET.name(), HEAD.name(), GETS.name(), HEADS.name(), POST.name(), PUT.name(), DELETE.name(), CRUD.name() + ); + /**是否为GET请求方法 * @param method * @param containPrivate 包含私密(非明文)获取方法GETS @@ -72,6 +83,14 @@ public static boolean isHeadMethod(RequestMethod method, boolean containPrivate) public static boolean isQueryMethod(RequestMethod method) { return isGetMethod(method, true) || isHeadMethod(method, true); } + + /**是否为更新(增删改)的请求方法 + * @param method + * @return 读操作(GET型或HEAD型) - false, 写操作(POST,PUT,DELETE) - true + */ + public static boolean isUpdateMethod(RequestMethod method) { + return ! isQueryMethod(method); + } /**是否为开放(不限制请求的结构或内容;明文,浏览器能直接访问及查看)的请求方法 * @param method @@ -81,6 +100,14 @@ public static boolean isPublicMethod(RequestMethod method) { return method == null || method == GET || method == HEAD; } + /**是否为私有(限制请求的结构或内容)的请求方法 + * @param method + * @return + */ + public static boolean isPrivateMethod(RequestMethod method) { + return ! isPublicMethod(method); + } + public static String getName(RequestMethod method) { return method == null ? GET.name() : method.name(); } diff --git a/APIJSONORM/src/main/java/apijson/SQL.java b/APIJSONORM/src/main/java/apijson/SQL.java index 391d5db48..6cec79bd2 100755 --- a/APIJSONORM/src/main/java/apijson/SQL.java +++ b/APIJSONORM/src/main/java/apijson/SQL.java @@ -116,6 +116,13 @@ public static String lengthCompare(String s, String compare) { public static String length(String s) { return "length(" + s + ")"; } + /** + * @param s 因为POWER(x,y)等函数含有不只一个key,所以需要客户端添加进去,服务端检测到条件中有'('和')'时就不转换,直接当SQL语句查询 + * @return "json_length(" + s + ")" + */ + public static String json_length(String s) { + return "json_length(" + s + ")"; + } /** * @param s 因为POWER(x,y)等函数含有不只一个key,所以需要客户端添加进去,服务端检测到条件中有'('和')'时就不转换,直接当SQL语句查询 * @return "char_length(" + s + ")" diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 795392e49..c6caf21e7 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -354,7 +354,7 @@ public static boolean isNotEmpty(String s, boolean trim) { PATTERN_ALPHA = Pattern.compile("^[a-zA-Z]+$"); PATTERN_ALPHA_BIG = Pattern.compile("^[A-Z]+$"); PATTERN_ALPHA_SMALL = Pattern.compile("^[a-z]+$"); - PATTERN_NAME = Pattern.compile("^[0-9a-zA-Z_]+$");//已用55个中英字符测试通过 + 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})(\\]?)$"); @@ -780,20 +780,28 @@ public static String getPrice(double price, int formatType) { } } + public static String join(String[] arr) { + return join(arr); + } /** 数组以指定分隔s拼接 * @param arr * @param s * @return */ public static String join(String[] arr, String s) { - StringBuilder stringBuilder = new StringBuilder(); + if (s == null) { + s = ","; + } + + StringBuilder sb = new StringBuilder(); for (int i = 0; i < arr.length; i++) { - stringBuilder.append(arr[i]); - if(i implements FunctionParser { + private static final String TAG = "AbstractFunctionParser"; + + /**是否解析参数 key 的对应的值,不用手动编码 curObj.getString(key) + */ + public static boolean IS_PARSE_ARG_VALUE = false; + + /**开启支持远程函数 + */ + public static boolean ENABLE_REMOTE_FUNCTION = true; + /**开启支持远程函数中的 JavaScript 脚本形式 + */ + public static boolean ENABLE_SCRIPT_FUNCTION = true; // // > + public static Map SCRIPT_EXECUTOR_MAP; public static Map FUNCTION_MAP; + static { FUNCTION_MAP = new HashMap<>(); + SCRIPT_EXECUTOR_MAP = new HashMap<>(); } private RequestMethod method; private String tag; private int version; private JSONObject request; + public AbstractFunctionParser() { this(null, null, 0, null); } + public AbstractFunctionParser(RequestMethod method, String tag, int version, @NotNull JSONObject request) { setMethod(method == null ? RequestMethod.GET : method); setTag(tag); @@ -48,122 +65,322 @@ public AbstractFunctionParser(RequestMethod method, String tag, int version, @No setRequest(request); } + private Parser parser; + + @Override + public Parser getParser() { + return parser; + } + + @Override + public AbstractFunctionParser setParser(Parser parser) { + this.parser = parser; + return this; + } + @Override public RequestMethod getMethod() { return method; } + @Override - public AbstractFunctionParser setMethod(RequestMethod method) { + public AbstractFunctionParser setMethod(RequestMethod method) { this.method = method; return this; } + @Override public String getTag() { return tag; } + @Override - public AbstractFunctionParser setTag(String tag) { + public AbstractFunctionParser setTag(String tag) { this.tag = tag; return this; } + @Override public int getVersion() { return version; } + @Override - public AbstractFunctionParser setVersion(int version) { + public AbstractFunctionParser setVersion(int version) { this.version = version; return this; } - + private String key; + @Override public String getKey() { return key; } + @Override - public AbstractFunctionParser setKey(String key) { + public AbstractFunctionParser setKey(String key) { this.key = key; return this; } - + private String parentPath; + @Override public String getParentPath() { return parentPath; } + @Override - public AbstractFunctionParser setParentPath(String parentPath) { + public AbstractFunctionParser setParentPath(String parentPath) { this.parentPath = parentPath; return this; } + private String currentName; + @Override public String getCurrentName() { return currentName; } + @Override - public AbstractFunctionParser setCurrentName(String currentName) { + public AbstractFunctionParser setCurrentName(String currentName) { this.currentName = currentName; return this; } - + @NotNull @Override public JSONObject getRequest() { return request; } + @Override - public AbstractFunctionParser setRequest(@NotNull JSONObject request) { + public AbstractFunctionParser setRequest(@NotNull JSONObject request) { this.request = request; return this; } - + private JSONObject currentObject; - @NotNull + + @NotNull @Override public JSONObject getCurrentObject() { return currentObject; } + @Override - public AbstractFunctionParser setCurrentObject(@NotNull JSONObject currentObject) { + public AbstractFunctionParser setCurrentObject(@NotNull JSONObject currentObject) { this.currentObject = currentObject; return this; } + /**根据路径取 Boolean 值 + * @param path + * @return + */ + public Boolean getArgBool(String path) { + return getArgVal(path, Boolean.class); + } + + /**根据路径取 Integer 值 + * @param path + * @return + */ + public Integer getArgInt(String path) { + return getArgVal(path, Integer.class); + } + + /**根据路径取 Long 值 + * @param path + * @return + */ + public Long getArgLong(String path) { + return getArgVal(path, Long.class); + } + + /**根据路径取 Float 值 + * @param path + * @return + */ + public Float getArgFloat(String path) { + return getArgVal(path, Float.class); + } + + /**根据路径取 Double 值 + * @param path + * @return + */ + public Double getArgDouble(String path) { + return getArgVal(path, Double.class); + } + + /**根据路径取 Number 值 + * @param path + * @return + */ + public Number getArgNum(String path) { + return getArgVal(path, Number.class); + } + + /**根据路径取 BigDecimal 值 + * @param path + * @return + */ + public BigDecimal getArgDecimal(String path) { + return getArgVal(path, BigDecimal.class); + } + + /**根据路径取 String 值 + * @param path + * @return + */ + public String getArgStr(String path) { + Object obj = getArgVal(path); + return JSON.toJSONString(obj); + } + + /**根据路径取 JSONObject 值 + * @param path + * @return + */ + public JSONObject getArgObj(String path) { + return getArgVal(path, JSONObject.class); + } + + /**根据路径取 JSONArray 值 + * @param path + * @return + */ + public JSONArray getArgArr(String path) { + return getArgVal(path, JSONArray.class); + } + + /**根据路径取 List 值 + * @param path + * @return + */ + public List getArgList(String path) { + return getArgList(path, null); + } + + /**根据路径取 List 值 + * @param path + * @return + */ + public List getArgList(String path, Class clazz) { + String s = getArgStr(path); + return JSON.parseArray(s, clazz); + } + + /**根据路径取值 + * @param path + * @return + * @param + */ + public T getArgVal(String path) { + return getArgVal(path, null); // 误判概率很小 false); + } + /**根据路径取值 + * @param path + * @param clazz + * @return + * @param + */ + public T getArgVal(String path, Class clazz) { + return getArgVal(path, clazz, true); + } + /**根据路径取值 + * @param path + * @param clazz + * @param tryAll false-仅当前对象,true-本次请求的全局对象以及 Parser 缓存值 + * @return + * @param + */ + public T getArgVal(String path, Class clazz, boolean tryAll) { + T val = getArgVal(getCurrentObject(), path, clazz); + if (tryAll == false || val != null) { + return val; + } + + Parser p = getParser(); + String targetPath = AbstractParser.getValuePath(getParentPath(), path); + return p == null ? null : (T) p.getValueByPath(targetPath); + } + /**根据路径从对象 obj 中取值 + * @param obj + * @param path + * @return + * @param + */ + public static T getArgVal(JSONObject obj, String path) { + return getArgVal(obj, path, null); + } + public static T getArgVal(JSONObject obj, String path, Class clazz) { + Object v = AbstractParser.getValue(obj, StringUtil.splitPath(path)); + return clazz == null ? (T) v : TypeUtils.cast(v, clazz, ParserConfig.getGlobalInstance()); + } + /**反射调用 * @param function 例如get(object,key),参数只允许引用,不能直接传值 * @param currentObject 不作为第一个参数,就不能远程调用invoke,避免死循环 - * @return {@link #invoke(AbstractFunctionParser, String, JSONObject)} + * @return {@link #invoke(String, JSONObject, boolean)} */ @Override public Object invoke(@NotNull String function, @NotNull JSONObject currentObject) throws Exception { - return invoke(this, function, currentObject); + return invoke(function, currentObject, false); + } + /**反射调用 + * @param function 例如get(object,key),参数只允许引用,不能直接传值 + * @param currentObject 不作为第一个参数,就不能远程调用invoke,避免死循环 + * @param containRaw 包含原始 SQL 片段 + * @return {@link #invoke(AbstractFunctionParser, String, JSONObject, boolean)} + */ + @Override + public Object invoke(@NotNull String function, @NotNull JSONObject currentObject, boolean containRaw) throws Exception { + return invoke(this, function, currentObject, containRaw); } - + /**反射调用 * @param parser - * @param request * @param function 例如get(Map:map,key),参数只允许引用,不能直接传值 - * @return {@link #invoke(AbstractFunctionParser, String, Class[], Object[])} + * @param currentObject + * @return {@link #invoke(AbstractFunctionParser, String, Class[], Object[])} */ - public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull String function, @NotNull JSONObject currentObject) throws Exception { + public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull String function, @NotNull JSONObject currentObject, boolean containRaw) throws Exception { + if (ENABLE_REMOTE_FUNCTION == false) { + throw new UnsupportedOperationException("AbstractFunctionParser.ENABLE_REMOTE_FUNCTION" + + " == false 时不支持远程函数!如需支持则设置 AbstractFunctionParser.ENABLE_REMOTE_FUNCTION = true !"); + } - FunctionBean fb = parseFunction(function, currentObject, false); + FunctionBean fb = parseFunction(function, currentObject, false, containRaw); - JSONObject row = FUNCTION_MAP.get(fb.getMethod()); + JSONObject row = FUNCTION_MAP.get(fb.getMethod()); //FIXME fb.getSchema() + "." + fb.getMethod() if (row == null) { throw new UnsupportedOperationException("不允许调用远程函数 " + fb.getMethod() + " !"); } - int v = row.getIntValue("version"); - if (parser.getVersion() < v) { - throw new UnsupportedOperationException("不允许 version = " + parser.getVersion() + " 的请求调用远程函数 " + fb.getMethod() + " ! 必须满足 version >= " + v + " !"); + String language = row.getString("language"); + String lang = "java".equalsIgnoreCase(language) ? null : language; + + if (ENABLE_SCRIPT_FUNCTION == false && lang != null) { + throw new UnsupportedOperationException("language = " + language + " 不合法!AbstractFunctionParser.ENABLE_SCRIPT_FUNCTION" + + " == false 时不支持远程函数中的脚本形式!如需支持则设置 AbstractFunctionParser.ENABLE_SCRIPT_FUNCTION = true !"); + } + + if (lang != null && SCRIPT_EXECUTOR_MAP.get(lang) == null) { + throw new ClassNotFoundException("找不到脚本语言 " + lang + " 对应的执行引擎!请先依赖相关库并在后端 APIJSONFunctionParser 中注册!"); } - String t = row.getString("tag"); - if (t != null && t.equals(parser.getTag()) == false) { - throw new UnsupportedOperationException("不允许 tag = " + parser.getTag() + " 的请求调用远程函数 " + fb.getMethod() + " ! 必须满足 tag = " + t + " !"); + + int version = row.getIntValue("version"); + if (parser.getVersion() < version) { + throw new UnsupportedOperationException("不允许 version = " + parser.getVersion() + " 的请求调用远程函数 " + fb.getMethod() + " ! 必须满足 version >= " + version + " !"); + } + String tag = row.getString("tag"); // TODO 改为 tags,类似 methods 支持多个 tag。或者干脆不要?因为目前非开放请求全都只能后端指定 + if (tag != null && tag.equals(parser.getTag()) == false) { + throw new UnsupportedOperationException("不允许 tag = " + parser.getTag() + " 的请求调用远程函数 " + fb.getMethod() + " ! 必须满足 tag = " + tag + " !"); } String[] methods = StringUtil.split(row.getString("methods")); List ml = methods == null || methods.length <= 0 ? null : Arrays.asList(methods); @@ -172,13 +389,15 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str } try { - return invoke(parser, fb.getMethod(), fb.getTypes(), fb.getValues()); - } catch (Exception e) { + return invoke(parser, fb.getMethod(), fb.getTypes(), fb.getValues(), row.getString("returnType"), currentObject, SCRIPT_EXECUTOR_MAP.get(lang)); + } + catch (Exception e) { if (e instanceof NoSuchMethodException) { - throw new IllegalArgumentException("字符 " + function + " 对应的远程函数 " + getFunction(fb.getMethod(), fb.getKeys()) + " 不在后端工程的DemoFunction内!" + throw new IllegalArgumentException("字符 " + function + " 对应的远程函数 " + getFunction(fb.getMethod(), fb.getKeys()) + + " 不在后端 " + parser.getClass().getName() + " 内,也不在父类中!如果需要则先新增对应方法!" + "\n请检查函数名和参数数量是否与已定义的函数一致!" + "\n且必须为 function(key0,key1,...) 这种单函数格式!" - + "\nfunction必须符合Java函数命名,key是用于在request内取值的键!" + + "\nfunction 必须符合 Java 函数命名,key 是用于在 curObj 内取值的键!" + "\n调用时不要有空格!" + (Log.DEBUG ? e.getMessage() : "")); } if (e instanceof InvocationTargetException) { @@ -194,51 +413,158 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str } } - + /**反射调用 - * @param methodName - * @param parameterTypes - * @param args - * @return - */ - public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull String methodName, @NotNull Class[] parameterTypes, @NotNull Object[] args) throws Exception { - return parser.getClass().getMethod(methodName, parameterTypes).invoke(parser, args); + * @param parser + * @param methodName + * @param parameterTypes + * @param args + * @return {@link #invoke(AbstractFunctionParser, String, Class[], Object[], String, JSONObject, ScriptExecutor)} + * @throws Exception + */ + public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull String methodName + , @NotNull Class[] parameterTypes, @NotNull Object[] args) throws Exception { + return invoke(parser, methodName, parameterTypes, args, null, null, null); + } + /**反射调用 + * @param parser + * @param methodName + * @param parameterTypes + * @param args + * @param returnType + * @param currentObject + * @param scriptExecutor + * @return + * @throws Exception + */ + public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull String methodName + , @NotNull Class[] parameterTypes, @NotNull Object[] args, String returnType + , JSONObject currentObject, ScriptExecutor scriptExecutor) throws Exception { + if (scriptExecutor != null) { + return invokeScript(parser, methodName, parameterTypes, args, returnType, currentObject, scriptExecutor); + } + + Method m = parser.getClass().getMethod(methodName, parameterTypes); // 不用判空,拿不到就会抛异常 + + if (Log.DEBUG) { + String rt = Log.DEBUG && m.getReturnType() != null ? m.getReturnType().getSimpleName() : null; + + if ("void".equals(rt)) { + rt = null; + } + if ("void".equals(returnType)) { + returnType = null; + } + + if (rt != returnType && (rt == null || rt.equals(returnType) == false)) { + throw new WrongMethodTypeException("远程函数 " + methodName + " 的实际返回值类型 " + rt + " 与 Function 表中的配置的 " + returnType + " 不匹配!"); + } + } + + return m.invoke(parser, args); } - /**解析函数 - * @param function - * @param request - * @param isSQLFunction - * @return - * @throws Exception - */ + /**Java 调用 JavaScript 函数 + * @param parser + * @param methodName + * @param parameterTypes + * @param args + * @param returnType + * @param currentObject + * @return + * @throws Exception + */ + public static Object invokeScript(@NotNull AbstractFunctionParser parser, @NotNull String methodName + , @NotNull Class[] parameterTypes, @NotNull Object[] args, String returnType, JSONObject currentObject, ScriptExecutor scriptExecutor) throws Exception { + Object result = scriptExecutor.execute(parser, currentObject, methodName, args); + if (Log.DEBUG && result != null) { + Class rt = result.getClass(); // 作为远程函数的 js 类型应该只有 JSON 的几种类型 + String fullReturnType = (StringUtil.isSmallName(returnType) + ? returnType : (returnType.startsWith("JSON") ? "com.alibaba.fastjson." : "java.lang.") + returnType); + + if ((rt == null && returnType != null) || (rt != null && returnType == null)) { + throw new WrongMethodTypeException("远程函数 " + methodName + " 的实际返回值类型 " + + (rt == null ? null : rt.getName()) + " 与 Function 表中的配置的 " + fullReturnType + " 不匹配!"); + } + + Class cls; + try { + cls = Class.forName(fullReturnType); + } + catch (Exception e) { + throw new WrongMethodTypeException("远程函数 " + methodName + " 在 Function 表中的配置的类型 " + + returnType + " 对应的 " + fullReturnType + " 错误!在 Java 中 Class.forName 找不到这个类型!"); + } + + if (cls.isAssignableFrom(rt) == false) { + throw new WrongMethodTypeException("远程函数 " + methodName + " 的实际返回值类型 " + + (rt == null ? null : rt.getName()) + " 与 Function 表中的配置的 " + + returnType + " 对应的 " + fullReturnType + " 不匹配!"); + } + } + + Log.d(TAG, "invokeScript " + methodName + "(..) >> result = " + result); + return result; + } + + + /**解析函数 + * @param function + * @param request + * @param isSQLFunction + * @return + * @throws Exception + */ @NotNull public static FunctionBean parseFunction(@NotNull String function, @NotNull JSONObject request, boolean isSQLFunction) throws Exception { + return parseFunction(function, request, isSQLFunction, false); + } + /**解析函数,自动解析的值类型只支持 Boolean, Number, String, Map, List + * @param function + * @param request + * @param isSQLFunction + * @param containRaw + * @return + * @throws Exception + */ + public static FunctionBean parseFunction(@NotNull String function, @NotNull JSONObject request, boolean isSQLFunction, boolean containRaw) throws Exception { int start = function.indexOf("("); int end = function.lastIndexOf(")"); String method = (start <= 0 || end != function.length() - 1) ? null : function.substring(0, start); - if (StringUtil.isEmpty(method, true)) { - throw new IllegalArgumentException("字符 " + function + " 不合法!函数的名称 function 不能为空," - + "且必须为 function(key0,key1,...) 这种单函数格式!" + + int dotInd = method == null ? -1 : method.indexOf("."); + String schema = dotInd < 0 ? null : method.substring(0, dotInd); + method = dotInd < 0 ? method : method.substring(dotInd + 1); + + if (StringUtil.isName(method) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!函数的名称 function 不能为空且必须符合方法命名规范!" + + "总体必须为 function(key0,key1,...) 这种单函数格式!" + "\nfunction必须符合 " + (isSQLFunction ? "SQL 函数/SQL 存储过程" : "Java 函数") + " 命名,key 是用于在 request 内取值的键!"); } + if (isSQLFunction != true && schema != null) { // StringUtil.isNotEmpty(schema, false)) { + throw new IllegalArgumentException("字符 " + schema + " 不合法!远程函数不允许指定类名!" + + "且必须为 function(key0,key1,...) 这种单函数格式!" + + "\nfunction必须符合 " + (isSQLFunction ? "SQL 函数/SQL 存储过程" : "Java 函数") + " 命名,key 是用于在 request 内取值的键!"); + } + if (schema != null) { // StringUtil.isName(schema) == false) { + schema = extractSchema(schema, null); + } String[] keys = StringUtil.split(function.substring(start + 1, end)); - int length = keys == null ? 0 : keys.length; Class[] types; Object[] values; - if (isSQLFunction) { + if (isSQLFunction || IS_PARSE_ARG_VALUE) { types = new Class[length]; values = new Object[length]; //碰到null就挂了!!!Number还得各种转换不灵活!不如直接传request和对应的key到函数里,函数内实现时自己 getLongValue,getJSONObject ... Object v; for (int i = 0; i < length; i++) { - v = values[i] = request.get(keys[i]); + v = values[i] = getArgValue(request, keys[i], containRaw); // request.get(keys[i]); if (v == null) { types[i] = Object.class; values[i] = null; @@ -247,24 +573,29 @@ public static FunctionBean parseFunction(@NotNull String function, @NotNull JSON if (v instanceof Boolean) { types[i] = Boolean.class; //只支持JSON的几种类型 - } + } // 怎么都有 bug,如果是引用的值,很多情况下无法指定 // 用 1L 指定为 Long ? 其它的默认按长度分配为 Integer 或 Long? + //else if (v instanceof Long || v instanceof Integer || v instanceof Short) { + // types[i] = Long.class; + //} else if (v instanceof Number) { types[i] = Number.class; } else if (v instanceof String) { types[i] = String.class; } - else if (v instanceof JSONObject) { // Map) { - types[i] = JSONObject.class; - //性能比较差 values[i] = request.getJSONObject(keys[i]); + else if (v instanceof Map) { // 泛型兼容? // JSONObject + types[i] = Map.class; + //性能比较差 + //values[i] = TypeUtils.cast(v, Map.class, ParserConfig.getGlobalInstance()); } - else if (v instanceof JSONArray) { // Collection) { - types[i] = JSONArray.class; - //性能比较差 values[i] = request.getJSONArray(keys[i]); + else if (v instanceof Collection) { // 泛型兼容? // JSONArray + types[i] = List.class; + //性能比较差 + values[i] = TypeUtils.cast(v, List.class, ParserConfig.getGlobalInstance()); } - else { //FIXME 碰到null就挂了!!! - throw new UnsupportedDataTypeException(keys[i] + ":value 中value不合法!远程函数 key():" + function + " 中的arg对应的值类型" - + "只能是 [Boolean, Number, String, JSONObject, JSONArray] 中的一种!"); + else { + throw new UnsupportedDataTypeException(keys[i] + ":value 中value不合法!远程函数 key():" + + function + " 中的 arg 对应的值类型只能是 [Boolean, Number, String, JSONObject, JSONArray] 中的一种!"); } } } @@ -283,6 +614,7 @@ else if (v instanceof JSONArray) { // Collection) { FunctionBean fb = new FunctionBean(); fb.setFunction(function); + fb.setSchema(schema); fb.setMethod(method); fb.setKeys(keys); fb.setTypes(types); @@ -291,6 +623,44 @@ else if (v instanceof JSONArray) { // Collection) { return fb; } + public static void verifySchema(String sch, String table) { + extractSchema(sch, table); + } + + public static String extractSchema(String sch, String table) { + if (StringUtil.isEmpty(sch)) { + return sch; + } + + if (table == null) { + table = "Table"; + } + + int ind = sch.indexOf("`"); + if (ind > 0) { + throw new IllegalArgumentException(table + ": { @key(): value } 对应存储过程 value 中字符 " + + sch + " 不合法!`schema` 当有 ` 包裹时一定是首尾各一个,不能多也不能少!"); + } + + if (ind == 0) { + sch = sch.substring(1); + if (sch.indexOf("`") != sch.length() - 1) { + throw new IllegalArgumentException(table + ": { @key(): value } 对应存储过程 value 中字符 `" + + sch + " 不合法!`schema` 当有 ` 包裹时一定是首尾各一个,不能多也不能少!"); + } + + sch = sch.substring(0, sch.length() - 1); + } + + if (PATTERN_SCHEMA.matcher(sch).matches() == false || sch.contains("--")) { + throw new IllegalArgumentException(table + ": { @key(): value } 对应存储过程 value 中字符 " + + sch + " 不合法!schema.function(arg) 中 schema 必须符合 数据库名/模式名 的命名规则!" + + "一般只能传英文字母、数字、下划线!不允许 -- 等可能导致 SQL 注入的符号!"); + } + + return sch; + } + /** * @param method @@ -311,9 +681,67 @@ public static String getFunction(String method, String[] keys) { return f; } + public static T getArgValue(@NotNull JSONObject currentObject, String keyOrValue) { + return getArgValue(currentObject, keyOrValue, false); + } + public static T getArgValue(@NotNull JSONObject currentObject, String keyOrValue, boolean containRaw) { + if (keyOrValue == null) { + return null; + } + + + if (keyOrValue.endsWith("`") && keyOrValue.substring(1).indexOf("`") == keyOrValue.length() - 2) { + return (T) currentObject.get(keyOrValue.substring(1, keyOrValue.length() - 1)); + } + + if (keyOrValue.endsWith("'") && keyOrValue.substring(1).indexOf("'") == keyOrValue.length() - 2) { + return (T) keyOrValue.substring(1, keyOrValue.length() - 1); + } + + // 传参加上 @raw:"key()" 避免意外情况 + Object val = containRaw ? AbstractSQLConfig.RAW_MAP.get(keyOrValue) : null; + if (val != null) { + return (T) ("".equals(val) ? keyOrValue : val); + } + + if (StringUtil.isName(keyOrValue.startsWith("@") ? keyOrValue.substring(1) : keyOrValue)) { + return (T) currentObject.get(keyOrValue); + } + + if ("true".equals(keyOrValue)) { + return (T) Boolean.TRUE; + } + if ("false".equals(keyOrValue)) { + return (T) Boolean.FALSE; + } + + // 性能更好,但居然非法格式也不报错 + //try { + // val = Boolean.valueOf(keyOrValue); // JSON.parse(keyOrValue); + // return (T) val; + //} + //catch (Throwable e) { + // Log.d(TAG, "getArgValue try {\n" + + // " val = Boolean.valueOf(keyOrValue);" + + // "} catch (Throwable e) = " + e.getMessage()); + //} + + try { + val = Double.valueOf(keyOrValue); // JSON.parse(keyOrValue); + return (T) val; + } + catch (Throwable e) { + Log.d(TAG, "getArgValue try {\n" + + " val = Double.valueOf(keyOrValue);" + + "} catch (Throwable e) = " + e.getMessage()); + } + + return (T) currentObject.get(keyOrValue); + } public static class FunctionBean { private String function; + private String schema; private String method; private String[] keys; private Class[] types; @@ -326,7 +754,14 @@ public void setFunction(String function) { this.function = function; } - public String getMethod() { + public String getSchema() { + return schema; + } + public void setSchema(String schema) { + this.schema = schema; + } + + public String getMethod() { return method; } public void setMethod(String method) { @@ -368,6 +803,8 @@ public String toFunctionCallString(boolean useValue) { * @return */ public String toFunctionCallString(boolean useValue, String quote) { + //String sch = getSchema(); + //String s = (StringUtil.isEmpty(sch) ? "" : sch + ".") + getMethod() + "("; String s = getMethod() + "("; Object[] args = useValue ? getValues() : getKeys(); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index e571cd8b4..0a35761cb 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -12,47 +12,48 @@ import apijson.StringUtil; import apijson.orm.AbstractFunctionParser.FunctionBean; import apijson.orm.exception.ConflictException; +import apijson.orm.exception.CommonException; import apijson.orm.exception.NotExistException; +import apijson.orm.exception.UnsupportedDataTypeException; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.serializer.SerializerFeature; -import javax.activation.UnsupportedDataTypeException; import java.rmi.ServerException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; import static apijson.JSONObject.KEY_COMBINE; import static apijson.JSONObject.KEY_DROP; import static apijson.JSONObject.KEY_TRY; +import static apijson.JSONRequest.KEY_QUERY; import static apijson.RequestMethod.POST; import static apijson.RequestMethod.PUT; import static apijson.orm.SQLConfig.TYPE_ITEM; - +import static apijson.RequestMethod.GET; /**简化Parser,getObject和getArray(getArrayConfig)都能用 * @author Lemon */ -public abstract class AbstractObjectParser implements ObjectParser { +public abstract class AbstractObjectParser implements ObjectParser { private static final String TAG = "AbstractObjectParser"; @NotNull - protected AbstractParser parser; - public AbstractObjectParser setParser(AbstractParser parser) { - this.parser = parser; + protected AbstractParser parser; + @Override + public AbstractParser getParser() { + return parser; + } + @Override + public AbstractObjectParser setParser(Parser parser) { + this.parser = (AbstractParser) parser; return this; } - protected JSONObject request;//不用final是为了recycle protected String parentPath;//不用final是为了recycle - protected SQLConfig arrayConfig;//不用final是为了recycle + protected SQLConfig arrayConfig;//不用final是为了recycle protected boolean isSubquery; protected final int type; @@ -68,10 +69,6 @@ public AbstractObjectParser setParser(AbstractParser parser) { protected final boolean drop; /**for single object - * @param parentPath - * @param request - * @param name - * @throws Exception */ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLConfig arrayConfig , boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception { @@ -108,7 +105,12 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLC request.remove(KEY_DROP); } - } + if (isTable) { + String raw = request.getString(JSONRequest.KEY_RAW); + String[] rks = StringUtil.split(raw); + rawKeyList = rks == null || rks.length <= 0 ? null : Arrays.asList(rks); + } + } @Override public String getParentPath() { @@ -121,6 +123,18 @@ public AbstractObjectParser setParentPath(String parentPath) { return this; } + protected JSONObject cache; + @Override + public JSONObject getCache() { + return cache; + } + + @Override + public AbstractObjectParser setCache(JSONObject cache) { + this.cache = cache; + return this; + } + protected int position; public int getPosition() { return position; @@ -155,7 +169,7 @@ public boolean isBreakParse() { protected JSONObject response; protected JSONObject sqlRequest; - protected JSONObject sqlReponse; + protected JSONObject sqlResponse; /** * 自定义关键词 */ @@ -175,6 +189,8 @@ public boolean isBreakParse() { private int objectCount; private int arrayCount; + + private List rawKeyList; /**解析成员 * response重新赋值 * @return null or this @@ -197,131 +213,113 @@ public AbstractObjectParser parse(String name, boolean isReuse) throws Exception breakParse = false; - response = new JSONObject(true);//must init - sqlReponse = null;//must init + response = new JSONObject(true); // must init + sqlResponse = null; // must init if (isReuse == false) { - sqlRequest = new JSONObject(true);//must init + sqlRequest = new JSONObject(true); // must init - customMap = null;//must init - functionMap = null;//must init - childMap = 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" 会在[]:{}前执行! + 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) { + 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++) { //去除 &,|,! 前缀 + 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方法! + // 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); // whereList.add(apijson.JSONRequest.KEY_USER_ID); // whereList.add(apijson.JSONRequest.KEY_USER_ID_IN); } - //条件>>>>>>>>>>>>>>>>>>> + // 条件>>>>>>>>>>>>>>>>>>> - String key; - Object value; int index = 0; + // hasOtherKeyNotFun = false; + JSONObject viceItem = null; for (Entry entry : set) { if (isBreakParse()) { break; } - value = entry.getValue(); + String key = entry == null ? null : entry.getKey(); + Object value = key == null ? null : entry.getValue(); if (value == null) { continue; } - key = entry.getKey(); + + // 处理url crud, 将crud 转换为真实method + RequestMethod _method = this.parser.getRealMethod(method, key, value); + // 没有执行校验流程的情况,比如url head, sql@子查询, sql@ method=GET + + Object obj = key.endsWith("@") ? request.get(key) : null; + if (obj instanceof JSONObject) { + ((JSONObject) obj).put(apijson.JSONObject.KEY_METHOD, GET); + } try { - if (key.startsWith("@") || key.endsWith("@") || (key.endsWith("<>") && value instanceof JSONObject)) { + boolean startsWithAt = key.startsWith("@"); + // if (startsWithAt || (key.endsWith("()") == false)) { + // hasOtherKeyNotFun = true; + // } + + if (startsWithAt || key.endsWith("@") || (key.endsWith("<>") && value instanceof JSONObject)) { if (onParse(key, value) == false) { invalidate(); } } else if (value instanceof JSONObject) { // JSONObject,往下一级提取 if (childMap != null) { // 添加到childMap,最后再解析 - childMap.put(key, (JSONObject)value); + childMap.put(key, (JSONObject) value); } else { // 直接解析并替换原来的,[]:{} 内必须直接解析,否则会因为丢掉count等属性,并且total@:"/[]/total"必须在[]:{} 后! - response.put(key, onChildParse(index, key, (JSONObject)value)); + JSON cache = index <= 0 || type != TYPE_ITEM || viceItem == null ? null : viceItem.getJSONObject(key); + JSON result = onChildParse(index, key, (JSONObject) value, cache); + if (index <= 0 && type == TYPE_ITEM) { + JSONObject mainItem = (JSONObject) result; + viceItem = result == null ? null : (JSONObject) mainItem.remove(AbstractSQLExecutor.KEY_VICE_ITEM); + } + + response.put(key, result); index ++; } } - else if ((method == POST || method == PUT) && value instanceof JSONArray + 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) + 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,直接填充 + else { // JSONArray 或其它 Object,直接填充 if (onParse(key, value) == false) { invalidate(); } } } 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.isMySQL()) { - db = SQLConfig.DATABASE_MYSQL; - } - else 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 = AbstractSQLConfig.DEFAULT_DATABASE; - } - } - - 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") - + " \n 数据库: " + db + " " + sqlConfig.getDBVersion() - + " \n JDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") - + " \n APIJSON: " + Log.VERSION - ); - } catch (Throwable e2) {} - } - - throw e; // 不忽略错误,抛异常 + throw CommonException.wrap(e, sqlConfig); // 不忽略错误,抛异常 } invalidate(); // 忽略错误,还原request } @@ -330,22 +328,42 @@ else if (sqlConfig.isClickHouse()) { } if (isTable) { - if (parser.getGlobalDatabase() != null && sqlRequest.get(JSONRequest.KEY_DATABASE) == null) { - sqlRequest.put(JSONRequest.KEY_DATABASE, parser.getGlobalDatabase()); + // parser.onVerifyRole 已处理 globalRole + + String db = parser.getGlobalDatabase(); + if (db != null) { + sqlRequest.putIfAbsent(JSONRequest.KEY_DATABASE, db); } - if (parser.getGlobalSchema() != null && sqlRequest.get(JSONRequest.KEY_SCHEMA) == null) { - sqlRequest.put(JSONRequest.KEY_SCHEMA, parser.getGlobalSchema()); + + String ds = parser.getGlobalDatasource(); + if (ds != null) { + sqlRequest.putIfAbsent(JSONRequest.KEY_DATASOURCE, ds); + } + + String ns = parser.getGlobalNamespace(); + if (ns != null) { + sqlRequest.putIfAbsent(JSONRequest.KEY_NAMESPACE, ns); } - if (parser.getGlobalDatasource() != null && sqlRequest.get(JSONRequest.KEY_DATASOURCE) == null) { - sqlRequest.put(JSONRequest.KEY_DATASOURCE, parser.getGlobalDatasource()); + + String cl = parser.getGlobalCatalog(); + if (cl != null) { + sqlRequest.putIfAbsent(JSONRequest.KEY_CATALOG, cl); + } + + String sch = parser.getGlobalSchema(); + if (sch != null) { + sqlRequest.putIfAbsent(JSONRequest.KEY_SCHEMA, sch); } - if (isSubquery == false) { //解决 SQL 语法报错,子查询不能 EXPLAIN - if (parser.getGlobalExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) { - sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobalExplain()); + if (isSubquery == false) { // 解决 SQL 语法报错,子查询不能 EXPLAIN + Boolean exp = parser.getGlobalExplain(); + if (sch != null) { + sqlRequest.putIfAbsent(JSONRequest.KEY_EXPLAIN, exp); } - if (parser.getGlobalCache() != null && sqlRequest.get(JSONRequest.KEY_CACHE) == null) { - sqlRequest.put(JSONRequest.KEY_CACHE, parser.getGlobalCache()); + + String cache = parser.getGlobalCache(); + if (cache != null) { + sqlRequest.putIfAbsent(JSONRequest.KEY_CACHE, cache); } } } @@ -368,8 +386,9 @@ else if (sqlConfig.isClickHouse()) { + //private boolean hasOtherKeyNotFun = false; - /**解析普通成员 + /**解析普通成员 * @param key * @param value * @return whether parse succeed @@ -383,12 +402,13 @@ public boolean onParse(@NotNull String key, @NotNull Object value) throws Except 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 (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, null); JSONObject obj = arr == null || arr.isEmpty() ? null : arr.getJSONObject(0); if (obj == null) { @@ -396,11 +416,26 @@ public boolean onParse(@NotNull String key, @NotNull Object value) throws Except } String from = subquery.getString(JSONRequest.KEY_SUBQUERY_FROM); - JSONObject arrObj = from == null ? null : obj.getJSONObject(from); + boolean isEmpty = StringUtil.isEmpty(from); + JSONObject arrObj = isEmpty ? null : obj.getJSONObject(from); + if (isEmpty) { + Set> set = obj.entrySet(); + for (Entry e : set) { + String k = e == null ? null : e.getKey(); + Object v = k == null ? null : e.getValue(); + if (v instanceof JSONObject && JSONRequest.isTableKey(k)) { + from = k; + arrObj = (JSONObject) v; + break; + } + } + } + if (arrObj == null) { - throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ from:value } 中 value 对应的主表对象 " + from + ":{} 不存在!"); + 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"); @@ -424,38 +459,55 @@ public boolean onParse(@NotNull String key, @NotNull Object value) throws Except else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 String replaceKey = key.substring(0, key.length() - 1); - // System.out.println("getObject key.endsWith(@) >> parseRelation = " + parseRelation); + // System.out.println("getObject key.endsWith(@) >> parseRelation = " + parseRelation); String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM ? path : parentPath, (String) value); - //先尝试获取,尽量保留缺省依赖路径,这样就不需要担心路径改变 + // 先尝试获取,尽量保留缺省依赖路径,这样就不需要担心路径改变 Object target = onReferenceParse(targetPath); Log.i(TAG, "onParse targetPath = " + targetPath + "; target = " + target); - if (target == null) {//String#equals(null)会出错 + 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 (Log.DEBUG) { + parser.putWarnIfNeed(AbstractParser.KEY_REF, path + "/" + key + ": " + targetPath + " 引用赋值获取路径对应的值为 null!请检查路径是否错误!"); } - } - if (targetPath.equals(target)) {//必须valuePath和保证getValueByPath传进去的一致! - Log.d(TAG, "onParse targetPath.equals(target) >>"); - //非查询关键词 @key 不影响查询,直接跳过 + // 非查询关键词 @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无影响 + // FIXME getCache() != null 时 return true,解决 RIGHT/OUTER/FOREIGN JOIN 主表无数据导致副表数据也不返回 + return false; // 获取不到就不用再做无效的 query 了。不考虑 Table:{Table:{}} 嵌套 } - } - //直接替换原来的key@:path为key:target + 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 ?以前可能因为防止二次遍历再解析,现在只有一次遍历 +// } +// } +// +// // 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无影响 +// } +// } + + // 直接替换原来的 key@: path 为 key: target Log.i(TAG, "onParse >> key = replaceKey; value = target;"); key = replaceKey; value = target; @@ -475,15 +527,12 @@ else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 String type; //远程函数比较少用,一般一个Table:{}内用到也就一两个,所以这里用 "-","0","+" 更直观,转用 -1,0,1 对性能提升不大。 boolean isMinus = k.endsWith("-"); + boolean isPlus = isMinus == false && k.endsWith("+"); if (isMinus) { //不能封装到functionMap后批量执行,否则会导致非Table内的 key-():function() 在onChildParse后执行! type = "-"; k = k.substring(0, k.length() - 1); - - if (isTable == false) { - parseFunction(k, (String) value, parentPath, name, request); - } } - else if (k.endsWith("+")) { + else if (isPlus) { type = "+"; k = k.substring(0, k.length() - 1); } @@ -491,7 +540,10 @@ else if (k.endsWith("+")) { type = "0"; } - if (isMinus == false || isTable) { + if (isPlus == false && isTable == false) { + parseFunction(key, k, (String) value, this.type == TYPE_ITEM ? path : parentPath, name, request, isMinus); + } + else { //远程函数比较少用,一般一个Table:{}内用到也就一两个,所以这里循环里new出来对性能影响不大。 Map map = functionMap.get(type); if (map == null) { @@ -503,7 +555,7 @@ else if (k.endsWith("+")) { } } else if (isTable && key.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(key) == false) { - customMap.put(key, value); + customMap.put(key, value); } else { sqlRequest.put(key, value); @@ -515,21 +567,22 @@ else if (isTable && key.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(k /** + * @param index * @param key * @param value - * @param isFirst + * @param cache * @return * @throws Exception */ @Override - public JSON onChildParse(int index, String key, JSONObject value) throws Exception { + public JSON onChildParse(int index, String key, JSONObject value, JSON cache) throws Exception { boolean isFirst = index <= 0; boolean isMain = isFirst && type == TYPE_ITEM; JSON child; boolean isEmpty; - if (apijson.JSONObject.isArrayKey(key)) {//APIJSON Array + if (apijson.JSONObject.isArrayKey(key)) { // APIJSON Array if (isMain) { throw new IllegalArgumentException(parentPath + "/" + key + ":{} 不合法!" + "数组 []:{} 中第一个 key:{} 必须是主表 TableKey:{} !不能为 arrayKey[]:{} !"); @@ -539,12 +592,31 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti arrayCount ++; int maxArrayCount = parser.getMaxArrayCount(); if (arrayCount > maxArrayCount) { - throw new IllegalArgumentException(path + " 内截至 " + key + ":{} 时数组对象 key[]:{} 的数量达到 " + arrayCount + " 已超限,必须在 0-" + maxArrayCount + " 内 !"); + throw new IllegalArgumentException(path + " 内截至 " + key + ":{} 时数组对象 key[]:{} " + + "的数量达到 " + arrayCount + " 已超限,必须在 0-" + maxArrayCount + " 内 !"); } } - child = parser.onArrayParse(value, path, key, isSubquery); + String query = value.getString(KEY_QUERY); + child = parser.onArrayParse(value, path, key, isSubquery, cache instanceof JSONArray ? (JSONArray) cache : null); isEmpty = child == null || ((JSONArray) child).isEmpty(); + + if ("2".equals(query) || "ALL".equals(query)) { // 不判断 isEmpty,因为分页数据可能只是某页没有 + String totalKey = JSONResponse.formatArrayKey(key) + "Total"; + String infoKey = JSONResponse.formatArrayKey(key) + "Info"; + if ((request.containsKey(totalKey) || request.containsKey(infoKey) + || request.containsKey(totalKey + "@") || request.containsKey(infoKey + "@")) == false) { + // onParse("total@", "/" + key + "/total"); + // onParse(infoKey + "@", "/" + key + "/info"); + // 替换为以下性能更好、对流程干扰最小的方式: + + String keyPath = AbstractParser.getValuePath(type == TYPE_ITEM ? path : parentPath, "/" + key); + String totalPath = keyPath + "/total"; + String infoPath = keyPath + "/info"; + response.put(totalKey, onReferenceParse(totalPath)); + response.put(infoKey, onReferenceParse(infoPath)); + } + } } else { //APIJSON Object boolean isTableKey = JSONRequest.isTableKey(Pair.parseEntry(key, true).getKey()); @@ -553,7 +625,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(); @@ -563,7 +635,8 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti } } - child = parser.onObjectParse(value, path, key, isMain ? arrayConfig.setType(SQLConfig.TYPE_ITEM_CHILD_0) : null, isSubquery); + child = parser.onObjectParse(value, path, key, isMain ? arrayConfig.setType(SQLConfig.TYPE_ITEM_CHILD_0) : null + , isSubquery, cache instanceof JSONObject ? (JSONObject) cache : null); isEmpty = child == null || ((JSONObject) child).isEmpty(); if (isFirst && isEmpty) { @@ -576,8 +649,8 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti } + //TODO 改用 MySQL json_add,json_remove,json_contains 等函数!不过就没有具体报错了,或许可以新增功能符,或者直接调 SQL 函数 - //TODO 改用 MySQL json_add,json_remove,json_contains 等函数! /**PUT key:[] * @param key * @param array @@ -605,7 +678,7 @@ public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throw //GET > add all 或 remove all > PUT > remove key //GET <<<<<<<<<<<<<<<<<<<<<<<<< - JSONObject rq = new JSONObject(); + JSONObject rq = new JSONObject(true); rq.put(JSONRequest.KEY_ID, request.get(JSONRequest.KEY_ID)); rq.put(JSONRequest.KEY_COLUMN, realKey); JSONObject rp = parseResponse(RequestMethod.GET, table, null, rq, null, false); @@ -613,40 +686,93 @@ public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throw //add all 或 remove all <<<<<<<<<<<<<<<<<<<<<<<<< - JSONArray targetArray = rp == null ? null : rp.getJSONArray(realKey); - if (targetArray == null) { + Object target = rp == null ? null : rp.get(realKey); + if (target instanceof String) { + try { + target = JSON.parse((String) target); + } catch (Throwable e) { + if (Log.DEBUG) { + Log.e(TAG, "try {\n" + + "\t\t\t\ttarget = JSON.parse((String) target);\n" + + "\t\t\t}\n" + + "\t\t\tcatch (Throwable e) = " + e.getMessage()); + } + } + } + + if (apijson.JSON.isBooleanOrNumberOrString(target)) { + throw new NullPointerException("PUT " + path + ", " + realKey + " 类型为 " + target.getClass().getSimpleName() + "," + + "不支持 Boolean, String, Number 等类型字段使用 'key+': [] 或 'key-': [] !" + + "对应字段在数据库的值必须为 JSONArray, JSONObject 中的一种!" + + "值为 JSONObject 类型时传参必须是 'key+': [{'key': value, 'key2': value2}] 或 'key-': ['key', 'key2'] !" + ); + } + + boolean isAdd = putType == 1; + + Collection targetArray = target instanceof Collection ? (Collection) target : null; + Map targetObj = target instanceof Map ? (Map) target : null; + + if (targetArray == null && targetObj == null) { + if (isAdd == false) { + throw new NullPointerException("PUT " + path + ", " + realKey + (target == null ? " 值为 null,不支持移除!" + : " 类型为 " + target.getClass().getSimpleName() + ",不支持这样移除!") + + "对应字段在数据库的值必须为 JSONArray, JSONObject 中的一种,且 key- 移除时,本身的值不能为 null!" + + "值为 JSONObject 类型时传参必须是 'key+': [{'key': value, 'key2': value2}] 或 'key-': ['key', 'key2'] !" + ); + } + targetArray = new JSONArray(); } - for (Object obj : array) { + + for (int i = 0; i < array.size(); i++) { + Object obj = array.get(i); if (obj == null) { continue; } - if (putType == 1) { - if (targetArray.contains(obj)) { - throw new ConflictException("PUT " + path + ", " + realKey + ":" + obj + " 已存在!"); + + if (isAdd) { + if (targetArray != null) { + if (targetArray.contains(obj)) { + throw new ConflictException("PUT " + path + ", " + key + "/" + i + " 已存在!"); + } + targetArray.add(obj); + } else { + if (obj != null && obj instanceof Map == false) { + throw new ConflictException("PUT " + path + ", " + key + "/" + i + " 必须为 JSONObject {} !"); + } + targetObj.putAll((Map) obj); } - targetArray.add(obj); - } else if (putType == 2) { - if (targetArray.contains(obj) == false) { - throw new NullPointerException("PUT " + path + ", " + realKey + ":" + obj + " 不存在!"); + } else { + if (targetArray != null) { + if (targetArray.contains(obj) == false) { + throw new NullPointerException("PUT " + path + ", " + key + "/" + i + " 不存在!"); + } + targetArray.remove(obj); + } else { + if (obj instanceof String == false) { + throw new ConflictException("PUT " + path + ", " + key + "/" + i + " 必须为 String 类型 !"); + } + if (targetObj.containsKey(obj) == false) { + throw new NullPointerException("PUT " + path + ", " + key + "/" + i + " 不存在!"); + } + targetObj.remove(obj); } - targetArray.remove(obj); } } //add all 或 remove all >>>>>>>>>>>>>>>>>>>>>>>>> //PUT <<<<<<<<<<<<<<<<<<<<<<<<< - sqlRequest.put(realKey, targetArray); + sqlRequest.put(realKey, targetArray != null ? targetArray : JSON.toJSONString(targetObj, SerializerFeature.WriteMapNullValue)); //PUT >>>>>>>>>>>>>>>>>>>>>>>>> } @Override - public void onTableArrayParse(String key, JSONArray value) throws Exception { + public void onTableArrayParse(String key, JSONArray valueArray) throws Exception { String childKey = key.substring(0, key.length() - JSONRequest.KEY_ARRAY.length()); - JSONArray valueArray = (JSONArray) value; int allCount = 0; JSONArray ids = new JSONArray(); @@ -654,60 +780,166 @@ public void onTableArrayParse(String key, JSONArray value) throws Exception { int version = parser.getVersion(); int maxUpdateCount = parser.getMaxUpdateCount(); - String idKey = parser.createSQLConfig().getIdKey(); //Table[]: [{}] arrayConfig 为 null + SQLConfig cfg = null; // 不能污染当前的配置 getSQLConfig(); + if (cfg == null) { // TODO 每次都创建成本比较高,是否新增 defaultInstance 或者 configInstance 用来专门 getIdKey 等? + cfg = parser.createSQLConfig(); + } + + String idKey = cfg.getIdKey(); //Table[]: [{}] arrayConfig 为 null boolean isNeedVerifyContent = parser.isNeedVerifyContent(); + cfg.setTable(childKey); // Request 表 structure 中配置 "ALLOW_PARTIAL_UPDATE_FAILED": "Table[],key[],key:alias[]" 自动配置 + boolean allowPartialFailed = cfg.allowPartialUpdateFailed(); + JSONArray failedIds = allowPartialFailed ? new JSONArray() : null; + + int firstFailIndex = -1; + JSONObject firstFailReq = null; + Throwable firstFailThrow = null; for (int i = 0; i < valueArray.size(); i++) { //只要有一条失败,则抛出异常,全部失败 //TODO 改成一条多 VALUES 的 SQL 性能更高,报错也更会更好处理,更人性化 JSONObject item; try { item = valueArray.getJSONObject(i); + if (item == null) { + throw new NullPointerException(); + } } catch (Exception e) { - throw new UnsupportedDataTypeException("批量新增/修改失败!" + key + "/" + i + ":value 中value不合法!类型必须是 OBJECT ,结构为 {} !"); - } - JSONRequest req = new JSONRequest(childKey, item); - - //parser.getMaxSQLCount() ? 可能恶意调用接口,把数据库拖死 - JSONObject result = (JSONObject) onChildParse(0, "" + i, isNeedVerifyContent == false ? req : parser.parseCorrectRequest(method, childKey, version, "", req, maxUpdateCount, parser)); - result = result.getJSONObject(childKey); - // - boolean success = JSONResponse.isSuccess(result); - int count = result == null ? null : result.getIntValue(JSONResponse.KEY_COUNT); - - if (success == false || count != 1) { //如果 code = 200 但 count != 1,不能算成功,掩盖了错误不好排查问题 - throw new ServerException("批量新增/修改失败!" + key + "/" + i + ":" + (success ? "成功但 count != 1 !" : (result == null ? "null" : result.getString(JSONResponse.KEY_MSG)))); + throw new UnsupportedDataTypeException( + "批量新增/修改失败!" + key + "/" + i + ":value 中value不合法!类型必须是 OBJECT ,结构为 {} !" + ); } - allCount += count; - ids.add(result.get(idKey)); + Object id = item.get(idKey); + JSONObject req = new JSONRequest(childKey, item); + JSONObject result = null; + try { + if (isNeedVerifyContent) { + req = parser.parseCorrectRequest(method, childKey, version, "", req, maxUpdateCount, parser); + } + //parser.getMaxSQLCount() ? 可能恶意调用接口,把数据库拖死 + result = (JSONObject) onChildParse(0, "" + i, req, null); + } + catch (Exception e) { + if (allowPartialFailed == false) { + throw e; + } + + if (firstFailThrow == null) { + firstFailThrow = e; + firstFailReq = valueArray.getJSONObject(i); // item + } + } + + result = result == null ? null : result.getJSONObject(childKey); + + boolean success = JSONResponse.isSuccess(result); + int count = result == null ? 0 : result.getIntValue(JSONResponse.KEY_COUNT); + if (id == null && result != null) { + id = result.get(idKey); + } + + if (success == false || count != 1) { //如果 code = 200 但 count != 1,不能算成功,掩盖了错误不好排查问题 + if (allowPartialFailed) { + failedIds.add(id); + if (firstFailIndex < 0) { + firstFailIndex = i; + } + } + else { + throw new ServerException( + "批量新增/修改失败!" + key + "/" + i + ":" + (success ? "成功但 count != 1 !" + : (result == null ? "null" : result.getString(JSONResponse.KEY_MSG)) + )); + } + } + + allCount += 1; // 加了 allowPartialFailed 后 count 可能为 0 allCount += count; + ids.add(id); } - JSONObject allResult = AbstractParser.newSuccessResult(); - allResult.put(JSONResponse.KEY_COUNT, allCount); - allResult.put(idKey + "[]", ids); + int failedCount = failedIds == null ? 0 : failedIds.size(); + if (failedCount > 0 && failedCount >= allCount) { + throw new ServerException("批量新增/修改 " + key + ":[] 中 " + allCount + " 个子项全部失败!" + + "第 " + firstFailIndex + " 项失败原因:" + (firstFailThrow == null ? "" : firstFailThrow.getMessage())); + } + + JSONObject allResult = AbstractParser.newSuccessResult(); + if (failedCount > 0) { + allResult.put("failedCount", failedCount); + allResult.put("failedIdList", failedIds); + + JSONObject failObj = new JSONObject(true); + failObj.put("index", firstFailIndex); + failObj.put(childKey, firstFailReq); + + if (firstFailThrow instanceof CommonException && firstFailThrow.getCause() != null) { + firstFailThrow = firstFailThrow.getCause(); + } + JSONObject obj = firstFailThrow == null ? failObj : AbstractParser.extendErrorResult(failObj, firstFailThrow, parser.isRoot()); + if (Log.DEBUG && firstFailThrow != null) { + obj.put("trace:throw", firstFailThrow.getClass().getName()); + obj.put("trace:stack", firstFailThrow.getStackTrace()); + } + + allResult.put("firstFailed", obj); + } + allResult.put(JSONResponse.KEY_COUNT, allCount); + allResult.put(idKey + "[]", ids); response.put(childKey, allResult); //不按原样返回,避免数据量过大 } @Override - public JSONObject parseResponse(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure) throws Exception { - SQLConfig config = newSQLConfig(method, table, alias, request, joinList, isProcedure); + public JSONObject parseResponse(RequestMethod method, String table, String alias + , JSONObject request, List joinList, boolean isProcedure) throws Exception { + SQLConfig config = newSQLConfig(method, table, alias, request, joinList, isProcedure) + .setParser(parser) + .setObjectParser(this); return parseResponse(config, isProcedure); } @Override - public JSONObject parseResponse(SQLConfig config, boolean isProcedure) throws Exception { + public JSONObject parseResponse(SQLConfig config, boolean isProcedure) throws Exception { if (parser.getSQLExecutor() == null) { parser.createSQLExecutor(); } + if (parser != null && config.getParser() == null) { + config.setParser(parser); + } return parser.getSQLExecutor().execute(config, isProcedure); } @Override public SQLConfig newSQLConfig(boolean isProcedure) throws Exception { - return newSQLConfig(method, table, alias, sqlRequest, joinList, isProcedure); + String raw = Log.DEBUG == false || sqlRequest == null ? null : sqlRequest.getString(apijson.JSONRequest.KEY_RAW); + String[] keys = raw == null ? null : StringUtil.split(raw); + if (keys != null && keys.length > 0) { + boolean allow = AbstractSQLConfig.ALLOW_MISSING_KEY_4_COMBINE; + + for (String key : keys) { + if (sqlRequest.get(key) != null) { + continue; + } + + String msg = "@raw:value 的 value 中 " + key + " 不合法!对应的 " + + key + ": value 在当前对象 " + name + " 不存在或 value = null,无法有效转为原始 SQL 片段!"; + + if (allow == false) { + throw new UnsupportedOperationException(msg); + } + + if (parser instanceof AbstractParser) { + ((AbstractParser) parser).putWarnIfNeed(JSONRequest.KEY_RAW, msg); + } + break; + } + } + + return newSQLConfig(method, table, alias, sqlRequest, joinList, isProcedure) + .setParser(parser) + .setObjectParser(this); } /**SQL 配置,for single object @@ -729,9 +961,11 @@ public AbstractObjectParser setSQLConfig(int count, int page, int position) thro try { sqlConfig = newSQLConfig(false); } - catch (NotExistException e) { - e.printStackTrace(); - return this; + catch (Exception e) { + if (e instanceof NotExistException || (e instanceof CommonException && e.getCause() instanceof NotExistException)) { + return this; + } + throw e; } } sqlConfig.setCount(sqlConfig.getCount() <= 0 ? count : sqlConfig.getCount()).setPage(page).setPosition(position); @@ -746,9 +980,6 @@ public AbstractObjectParser setSQLConfig(int count, int page, int position) thro protected SQLConfig sqlConfig = null;//array item复用 /**SQL查询,for array item - * @param count - * @param page - * @param position * @return this * @throws Exception */ @@ -756,32 +987,37 @@ public AbstractObjectParser setSQLConfig(int count, int page, int position) thro 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; - // } - } - } - - if (drop) {//丢弃Table,只为了向下提供条件 - sqlReponse = null; + sqlResponse = new JSONObject(sqlRequest); } - - return this; + else { + try { + sqlResponse = onSQLExecute(); + } + catch (Exception e) { + if (e instanceof NotExistException || (e instanceof CommonException && e.getCause() instanceof NotExistException)) { + // Log.e(TAG, "getObject try { response = getSQLObject(config2); } catch (Exception e) {"); + // if (e instanceof NotExistException) {//非严重异常,有时候只是数据不存在 + // // e.printStackTrace(); + sqlResponse = 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 { + throw e; + } + } + } + + if (drop) {//丢弃Table,只为了向下提供条件 + sqlResponse = null; + } + + return this; } /** @@ -790,12 +1026,12 @@ public AbstractObjectParser executeSQL() throws Exception { */ @Override public JSONObject response() throws Exception { - if (sqlReponse == null || sqlReponse.isEmpty()) { + if (sqlResponse == null || sqlResponse.isEmpty()) { if (isTable) {//Table自身都获取不到值,则里面的Child都无意义,不需要再解析 return null; // response; } } else { - response.putAll(sqlReponse); + response.putAll(sqlResponse); } @@ -824,35 +1060,59 @@ 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 是实时执行,而不是在这里批量执行 + boolean isMinus = "-".equals(type); + JSONObject json = isMinus ? sqlRequest : response; // key-():function 是实时执行,而不是在这里批量执行 for (Entry entry : functionSet) { - parseFunction(entry.getKey(), entry.getValue(), parentPath, name, json); + parseFunction(entry.getKey(), entry.getKey(), entry.getValue(), this.type == TYPE_ITEM ? path : parentPath, name, json, isMinus); } } } - public void parseFunction(String key, String value, String parentPath, String currentName, JSONObject currentObject) throws Exception { + + //public void parseFunction(String key, String value, String parentPath, String currentName, JSONObject currentObject) throws Exception { + // parseFunction(key, value, parentPath, currentName, currentObject, false); + //} + public void parseFunction(String rawKey, String key, String value, String parentPath + , String currentName, JSONObject currentObject, boolean isMinus) throws Exception { Object result; - if (key.startsWith("@")) { - FunctionBean fb = AbstractFunctionParser.parseFunction(value, currentObject, true); + boolean containRaw = rawKeyList != null && rawKeyList.contains(rawKey); + + boolean isProcedure = key.startsWith("@"); + if (isProcedure) { + FunctionBean fb = AbstractFunctionParser.parseFunction(value, currentObject, true, containRaw); SQLConfig config = newSQLConfig(true); + String sch = fb.getSchema(); + if (StringUtil.isNotEmpty(sch, true)) { + config.setSchema(sch); + } config.setProcedure(fb.toFunctionCallString(true)); result = parseResponse(config, true); key = key.substring(1); } else { - result = parser.onFunctionParse(key, value, parentPath, currentName, currentObject); + result = parser.onFunctionParse(key, value, parentPath, currentName, currentObject, containRaw); } - if (result != null) { - String k = AbstractSQLConfig.getRealKey(method, key, false, false); + String k = AbstractSQLConfig.getRealKey(method, key, false, false); - response.put(k, result); - parser.putQueryResult(AbstractParser.getAbsPath(path, k), result); - } + if (isProcedure == false && isMinus) { + if (result != null) { + sqlRequest.put(k, result); + } else { + sqlRequest.remove(k); + } + } + + if (result != null) { + response.put(k, result); + } else { + response.remove(k); + } + + parser.putQueryResult(AbstractParser.getAbsPath(path, k), result); } @Override @@ -862,7 +1122,7 @@ public void onChildResponse() throws Exception { if (set != null) { int index = 0; for (Entry entry : set) { - Object child = entry == null ? null : onChildParse(index, entry.getKey(), entry.getValue()); + Object child = entry == null ? null : onChildParse(index, entry.getKey(), entry.getValue(), null); if (child == null || (child instanceof JSONObject && ((JSONObject) child).isEmpty()) || (child instanceof JSONArray && ((JSONArray) child).isEmpty()) @@ -888,56 +1148,60 @@ public Object onReferenceParse(@NotNull String path) { public JSONObject onSQLExecute() throws Exception { int position = getPosition(); - JSONObject result; - if (isArrayMainTable && position > 0) { // 数组主表使用专门的缓存数据 + JSONObject result = getCache(); + if (result != null) { + parser.putQueryResult(path, result); + } + else if (isArrayMainTable && position > 0) { // 数组主表使用专门的缓存数据 result = parser.getArrayMainCacheItem(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2), position); } else { result = parser.executeSQL(sqlConfig, isSubquery); boolean isSimpleArray = false; - List rawList = null; + // 提取并缓存数组主表的列表数据 + List rawList = result == null ? null : (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); - if (isArrayMainTable && position == 0 && result != null) { + if (isArrayMainTable && position == 0 && rawList != null) { - isSimpleArray = (functionMap == null || functionMap.isEmpty()) - && (customMap == null || customMap.isEmpty()) - && (childMap == null || childMap.isEmpty()) - && (table.equals(arrayTable)); + isSimpleArray = (functionMap == null || functionMap.isEmpty()) + && (customMap == null || customMap.isEmpty()) + && (childMap == null || childMap.isEmpty()) + && (table.equals(arrayTable)); - // 提取并缓存数组主表的列表数据 - rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); - if (rawList != null) { - String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); + // APP JOIN 副表时副表返回了这个字段 rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); + String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); - if (isSimpleArray == false) { - long startTime = System.currentTimeMillis(); + if (isSimpleArray == false) { + long startTime = System.currentTimeMillis(); - for (int i = 1; i < rawList.size(); i++) { // 从 1 开始,0 已经处理过 - JSONObject obj = rawList.get(i); + for (int i = 1; i < rawList.size(); i++) { // 从 1 开始,0 已经处理过 + JSONObject obj = rawList.get(i); - if (obj != null) { - parser.putQueryResult(arrayPath + "/" + i + "/" + name, obj); // 解决获取关联数据时requestObject里不存在需要的关联数据 - } - } + if (obj != null) { + // obj.remove(AbstractSQLExecutor.KEY_VICE_ITEM); + parser.putQueryResult(arrayPath + "/" + i + "/" + name, obj); // 解决获取关联数据时requestObject里不存在需要的关联数据 + } + } - long endTime = System.currentTimeMillis(); // 3ms - 8ms - Log.e(TAG, "\n onSQLExecute <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n for (int i = 1; i < list.size(); i++) startTime = " + startTime - + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n "); - } + long endTime = System.currentTimeMillis(); // 3ms - 8ms + Log.e(TAG, "\n onSQLExecute <<<<<<<<<<<<<<<<<<<<<<<<<<<<" + + "\n for (int i = 1; i < list.size(); i++) startTime = " + startTime + + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n "); + } - parser.putArrayMainCache(arrayPath, rawList); - } - } + parser.putArrayMainCache(arrayPath, rawList); + } - if (isSubquery == false && result != null) { - parser.putQueryResult(path, result); // 解决获取关联数据时requestObject里不存在需要的关联数据 + if (isSubquery == false && result != null) { + parser.putQueryResult(path, result); // 解决获取关联数据时requestObject里不存在需要的关联数据 - if (isSimpleArray && rawList != null) { - result.put(AbstractSQLExecutor.KEY_RAW_LIST, rawList); - } - } - } + if (isSimpleArray && rawList != null) { + result.put(AbstractSQLExecutor.KEY_RAW_LIST, rawList); + } + } + } return result; } @@ -976,7 +1240,7 @@ public void recycle() { request = null; response = null; sqlRequest = null; - sqlReponse = null; + sqlResponse = null; functionMap = null; customMap = null; @@ -994,7 +1258,7 @@ public AbstractObjectParser setMethod(RequestMethod method) { if (this.method != method) { this.method = method; sqlConfig = null; - //TODO ? sqlReponse = null; + //TODO ? sqlResponse = null; } return this; } @@ -1023,13 +1287,13 @@ public String getAlias() { return alias; } @Override - public SQLConfig getArrayConfig() { + public SQLConfig getArrayConfig() { return arrayConfig; } @Override - public SQLConfig getSQLConfig() { + public SQLConfig getSQLConfig() { return sqlConfig; } @@ -1042,8 +1306,8 @@ public JSONObject getSqlRequest() { return sqlRequest; } @Override - public JSONObject getSqlReponse() { - return sqlReponse; + public JSONObject getSqlResponse() { + return sqlResponse; } @Override diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 5135a7279..624830374 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -5,34 +5,26 @@ package apijson.orm; -import static apijson.JSONObject.KEY_EXPLAIN; -import static apijson.RequestMethod.GET; +import apijson.orm.exception.ConflictException; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; import java.io.UnsupportedEncodingException; import java.lang.management.ManagementFactory; import java.net.InetAddress; +import java.net.URLDecoder; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.SQLException; import java.sql.Savepoint; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; -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; import apijson.JSON; import apijson.JSONRequest; @@ -41,18 +33,24 @@ import apijson.NotNull; import apijson.RequestMethod; import apijson.StringUtil; -import apijson.orm.exception.ConditionErrorException; -import apijson.orm.exception.ConflictException; -import apijson.orm.exception.NotExistException; -import apijson.orm.exception.NotLoggedInException; -import apijson.orm.exception.OutOfRangeException; +import apijson.orm.exception.CommonException; +import apijson.orm.exception.UnsupportedDataTypeException; + +import static apijson.JSONObject.KEY_COMBINE; +import static apijson.JSONObject.KEY_EXPLAIN; +import static apijson.RequestMethod.CRUD; +import static apijson.RequestMethod.GET; -/**parser for parsing request to JSONObject +/**Parser for parsing request to JSONObject * @author Lemon */ public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { protected static final String TAG = "AbstractParser"; - + + /** + * JSON 对象、数组对应的数据源、版本、角色、method等 + */ + protected Map> keyObjectAttributesMap = new HashMap<>(); /** * 可以通过切换该变量来控制是否打印关键的接口请求内容。保守起见,该值默认为false。 * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求内容。 @@ -72,24 +70,35 @@ public abstract class AbstractParser implements Parser, Par public static boolean IS_PRINT_REQUEST_ENDTIME_LOG = false; - public static int DEFAULT_QUERY_COUNT = 10; + /** + * 分页页码是否从 1 开始,默认为从 0 开始 + */ + public static boolean IS_START_FROM_1 = false; public static int MAX_QUERY_PAGE = 100; + public static int DEFAULT_QUERY_COUNT = 10; 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; - + + public boolean isStartFrom1() { + return IS_START_FROM_1; + } @Override - public int getDefaultQueryCount() { - return DEFAULT_QUERY_COUNT; + public int getMinQueryPage() { + return isStartFrom1() ? 1 : 0; } @Override public int getMaxQueryPage() { return MAX_QUERY_PAGE; } @Override + public int getDefaultQueryCount() { + return DEFAULT_QUERY_COUNT; + } + @Override public int getMaxQueryCount() { return MAX_QUERY_COUNT; } @@ -122,13 +131,16 @@ public AbstractParser() { this(null); } /**needVerify = true - * @param requestMethod null ? requestMethod = GET + * @param method null ? requestMethod = GET */ public AbstractParser(RequestMethod method) { - this(method, true); + super(); + setMethod(method); + setNeedVerifyRole(AbstractVerifier.ENABLE_VERIFY_ROLE); + setNeedVerifyContent(AbstractVerifier.ENABLE_VERIFY_CONTENT); } /** - * @param requestMethod null ? requestMethod = GET + * @param method null ? requestMethod = GET * @param needVerify 仅限于为服务端提供方法免验证特权,普通请求不要设置为 false ! 如果对应Table有权限也建议用默认值 true,保持和客户端权限一致 */ public AbstractParser(RequestMethod method, boolean needVerify) { @@ -136,7 +148,7 @@ public AbstractParser(RequestMethod method, boolean needVerify) { setMethod(method); setNeedVerify(needVerify); } - + protected boolean isRoot = true; public boolean isRoot() { return isRoot; @@ -145,7 +157,58 @@ public AbstractParser setRoot(boolean isRoot) { this.isRoot = isRoot; return this; } - + + public static final String KEY_REF = "Reference"; + + /**警告信息 + * Map<"Reference", "引用赋值获取路径 /Comment/userId 对应的值为 null!"> + */ + protected Map warnMap = new LinkedHashMap<>(); + public String getWarn(String type) { + return warnMap == null ? null : warnMap.get(type); + } + public AbstractParser putWarnIfNeed(String type, String warn) { + if (Log.DEBUG) { + String w = getWarn(type); + if (StringUtil.isEmpty(w, true)) { + putWarn(type, warn); + } + } + return this; + } + public AbstractParser putWarn(String type, String warn) { + if (warnMap == null) { + warnMap = new LinkedHashMap<>(); + } + warnMap.put(type, warn); + return this; + } + /**获取警告信息 + * @return + */ + public String getWarnString() { + Set> set = warnMap == null ? null : warnMap.entrySet(); + if (set == null || set.isEmpty()) { + return null; + } + + StringBuilder sb = new StringBuilder(); + for (Entry e : set) { + String k = e == null ? null : e.getKey(); + String v = k == null ? null : e.getValue(); + if (StringUtil.isEmpty(v, true)) { + continue; + } + + if (StringUtil.isNotEmpty(k, true)) { + sb.append("[" + k + "]: "); + } + sb.append(v + "; "); + } + + return sb.toString(); + } + @NotNull protected Visitor visitor; @@ -257,15 +320,7 @@ public AbstractParser setGlobalDatabase(String globalDatabase) { public String getGlobalDatabase() { return globalDatabase; } - protected String globalSchema; - public AbstractParser setGlobalSchema(String globalSchema) { - this.globalSchema = globalSchema; - return this; - } - @Override - public String getGlobalSchema() { - return globalSchema; - } + protected String globalDatasource; @Override public String getGlobalDatasource() { @@ -276,6 +331,36 @@ public AbstractParser setGlobalDatasource(String globalDatasource) { return this; } + protected String globalNamespace; + public AbstractParser setGlobalNamespace(String globalNamespace) { + this.globalNamespace = globalNamespace; + return this; + } + @Override + public String getGlobalNamespace() { + return globalNamespace; + } + + protected String globalCatalog; + public AbstractParser setGlobalCatalog(String globalCatalog) { + this.globalCatalog = globalCatalog; + return this; + } + @Override + public String getGlobalCatalog() { + return globalCatalog; + } + + protected String globalSchema; + public AbstractParser setGlobalSchema(String globalSchema) { + this.globalSchema = globalSchema; + return this; + } + @Override + public String getGlobalSchema() { + return globalSchema; + } + protected Boolean globalExplain; public AbstractParser setGlobalExplain(Boolean globalExplain) { this.globalExplain = globalExplain; @@ -335,9 +420,6 @@ public AbstractParser setNeedVerifyContent(boolean needVerifyContent) { } - - - protected SQLExecutor sqlExecutor; protected Verifier verifier; protected Map queryResultMap;//path-result @@ -346,6 +428,7 @@ public AbstractParser setNeedVerifyContent(boolean needVerifyContent) { public SQLExecutor getSQLExecutor() { if (sqlExecutor == null) { sqlExecutor = createSQLExecutor(); + sqlExecutor.setParser(this); } return sqlExecutor; } @@ -409,6 +492,17 @@ public JSONObject parseResponse(JSONObject request) { + "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n\n "); requestObject = request; + try { + setVersion(requestObject.getIntValue(JSONRequest.KEY_VERSION)); + requestObject.remove(JSONRequest.KEY_VERSION); + + if (getMethod() != RequestMethod.CRUD) { + setTag(requestObject.getString(JSONRequest.KEY_TAG)); + requestObject.remove(JSONRequest.KEY_TAG); + } + } catch (Exception e) { + return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot); + } verifier = createVerifier().setVisitor(getVisitor()); @@ -436,19 +530,25 @@ public JSONObject parseResponse(JSONObject request) { } try { - setGlobalFormat(requestObject.getBoolean(JSONRequest.KEY_FORMAT)); setGlobalDatabase(requestObject.getString(JSONRequest.KEY_DATABASE)); - setGlobalSchema(requestObject.getString(JSONRequest.KEY_SCHEMA)); setGlobalDatasource(requestObject.getString(JSONRequest.KEY_DATASOURCE)); + setGlobalNamespace(requestObject.getString(JSONRequest.KEY_NAMESPACE)); + setGlobalCatalog(requestObject.getString(JSONRequest.KEY_CATALOG)); + setGlobalSchema(requestObject.getString(JSONRequest.KEY_SCHEMA)); + setGlobalExplain(requestObject.getBoolean(JSONRequest.KEY_EXPLAIN)); setGlobalCache(requestObject.getString(JSONRequest.KEY_CACHE)); + setGlobalFormat(requestObject.getBoolean(JSONRequest.KEY_FORMAT)); - requestObject.remove(JSONRequest.KEY_FORMAT); requestObject.remove(JSONRequest.KEY_DATABASE); - requestObject.remove(JSONRequest.KEY_SCHEMA); requestObject.remove(JSONRequest.KEY_DATASOURCE); + requestObject.remove(JSONRequest.KEY_NAMESPACE); + requestObject.remove(JSONRequest.KEY_CATALOG); + requestObject.remove(JSONRequest.KEY_SCHEMA); + requestObject.remove(JSONRequest.KEY_EXPLAIN); requestObject.remove(JSONRequest.KEY_CACHE); + requestObject.remove(JSONRequest.KEY_FORMAT); } catch (Exception e) { return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot); } @@ -459,13 +559,13 @@ public JSONObject parseResponse(JSONObject request) { queryResultMap = new HashMap(); Exception error = null; - sqlExecutor = createSQLExecutor(); + sqlExecutor = getSQLExecutor(); onBegin(); try { queryDepth = 0; executedSQLDuration = 0; - - requestObject = onObjectParse(request, null, null, null, false); + + requestObject = onObjectParse(request, null, null, null, false, null); onCommit(); } @@ -476,30 +576,37 @@ public JSONObject parseResponse(JSONObject request) { onRollback(); } - requestObject = error == null ? extendSuccessResult(requestObject, isRoot) : extendErrorResult(requestObject, error, requestMethod, getRequestURL(), isRoot); + String warn = Log.DEBUG == false || error != null ? null : getWarnString(); + + requestObject = error == null ? extendSuccessResult(requestObject, warn, isRoot) : extendErrorResult(requestObject, error, requestMethod, getRequestURL(), isRoot); JSONObject res = (globalFormat != null && globalFormat) && JSONResponse.isSuccess(requestObject) ? new JSONResponse(requestObject) : requestObject; long endTime = System.currentTimeMillis(); long duration = endTime - startTime; + res.putIfAbsent("time", endTime); 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); if (error != null) { - res.put("trace:throw", error.getClass().getName()); - res.put("trace:stack", error.getStackTrace()); + // String msg = error.getMessage(); + // if (msg != null && msg.contains(Log.KEY_SYSTEM_INFO_DIVIDER)) { + // } + Throwable t = error instanceof CommonException && error.getCause() != null ? error.getCause() : error; + res.put("trace:throw", t.getClass().getName()); + res.put("trace:stack", t.getStackTrace()); } } onClose(); - //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/232 + // 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"); @@ -529,7 +636,7 @@ public void onVerifyContent() throws Exception { * @throws Exception */ @Override - public void onVerifyRole(@NotNull SQLConfig config) throws Exception { + public void onVerifyRole(@NotNull SQLConfig config) throws Exception { if (Log.DEBUG) { Log.i(TAG, "onVerifyRole config = " + JSON.toJSONString(config)); } @@ -551,7 +658,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 { @@ -570,31 +677,9 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers return request;//需要指定JSON结构的get请求可以改为post请求。一般只有对安全性要求高的才会指定,而这种情况用明文的GET方式几乎肯定不安全 } - if (StringUtil.isEmpty(tag, true)) { - throw new IllegalArgumentException("请在最外层传 tag !一般是 Table 名,例如 \"tag\": \"User\" "); - } - - //获取指定的JSON结构 <<<<<<<<<<<< - JSONObject object = null; - String error = ""; - try { - object = getStructure("Request", method.name(), tag, version); - } catch (Exception e) { - error = e.getMessage(); - } - if (object == null) { //empty表示随意操作 || object.isEmpty()) { - throw new UnsupportedOperationException("找不到 version: " + version + ", method: " + method.name() + ", tag: " + tag + " 对应的 structure !" - + "非开放请求必须是后端 Request 表中校验规则允许的操作!\n " + error + "\n如果需要则在 Request 表中新增配置!"); - } - - //获取指定的JSON结构 >>>>>>>>>>>>>> - JSONObject target = wrapRequest(method, tag, object, true); - - //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} - return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobalDatabase(), getGlobalSchema(), creator); + return batchVerify(method, tag, version, name, request, maxUpdateCount, creator); } - - + /**自动根据 tag 是否为 TableKey 及是否被包含在 object 内来决定是否包装一层,改为 { tag: object, "tag": tag } * @param object * @param tag @@ -624,7 +709,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 { @@ -669,50 +754,68 @@ else if (target.containsKey(key) == false) { * @return */ public static JSONObject newResult(int code, String msg) { - return newResult(code, msg, false); + return newResult(code, msg, null); } - /**新建带状态内容的JSONObject + + /** + * 添加JSONObject的状态内容,一般用于错误提示结果 + * + * @param code + * @param msg + * @param warn + * @return + */ + public static JSONObject newResult(int code, String msg, String warn) { + return newResult(code, msg, warn, false); + } + + /** + * 新建带状态内容的JSONObject + * * @param code * @param msg + * @param warn * @param isRoot * @return */ - public static JSONObject newResult(int code, String msg, boolean isRoot) { - return extendResult(null, code, msg, isRoot); + public static JSONObject newResult(int code, String msg, String warn, boolean isRoot) { + return extendResult(null, code, msg, warn, isRoot); } - - /**添加JSONObject的状态内容,一般用于错误提示结果 + + /** + * 添加JSONObject的状态内容,一般用于错误提示结果 + * * @param object * @param code * @param msg * @return */ - public static JSONObject extendResult(JSONObject object, int code, String msg, boolean isRoot) { + public static JSONObject extendResult(JSONObject object, int code, String msg, String warn, 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 提 bug 请发请求和响应的【完整截屏】,没图的自行解决!" - + " \n 开发者有限的时间和精力主要放在【维护项目源码和文档】上!" - + " \n 【描述不详细】 或 【文档/常见问题 已有答案】 的问题可能会被忽略!!" - + " \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") - + " \n APIJSON: " + Log.VERSION - + " \n | \n 常见问题:https://github.com/Tencent/APIJSON/issues/36" - + " \n 通用文档:https://github.com/Tencent/APIJSON/blob/master/Document.md" - + " \n 视频教程:https://search.bilibili.com/all?keyword=APIJSON"); - + + " \n系统: " + Log.OS_NAME + " " + Log.OS_VERSION + + " \n数据库: DEFAULT_DATABASE = " + AbstractSQLConfig.DEFAULT_DATABASE + + " \nJDK: " + Log.JAVA_VERSION + " " + Log.OS_ARCH + + " \nAPIJSON: " + Log.VERSION + + " \n \n【常见问题】:https://github.com/Tencent/APIJSON/issues/36" + + " \n【通用文档】:https://github.com/Tencent/APIJSON/blob/master/Document.md" + + " \n【视频教程】:https://search.bilibili.com/all?keyword=APIJSON"); + msg = index >= 0 ? msg.substring(0, index) : msg; - + if (object == null) { object = new JSONObject(true); } - - if (object.containsKey(JSONResponse.KEY_OK) == false) { + + if (object.get(JSONResponse.KEY_OK) == null) { object.put(JSONResponse.KEY_OK, JSONResponse.isSuccess(code)); } - if (object.containsKey(JSONResponse.KEY_CODE) == false) { + if (object.get(JSONResponse.KEY_CODE) == null) { object.put(JSONResponse.KEY_CODE, code); } @@ -720,52 +823,72 @@ 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) { + if (StringUtil.isNotEmpty(warn, true)) { + debug += "\n 【警告】:" + warn; + } object.put("debug:info|help", debug); } - + return object; } - /**添加请求成功的状态内容 + /** + * 添加请求成功的状态内容 + * * @param object * @return */ public static JSONObject extendSuccessResult(JSONObject object) { return extendSuccessResult(object, false); } + + public static JSONObject extendSuccessResult(JSONObject object, boolean isRoot) { + return extendSuccessResult(object, null, isRoot); + } + /**添加请求成功的状态内容 * @param object * @param isRoot * @return */ - public static JSONObject extendSuccessResult(JSONObject object, boolean isRoot) { - return extendResult(object, JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, isRoot); + public static JSONObject extendSuccessResult(JSONObject object, String warn, boolean isRoot) { + return extendResult(object, JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, warn, isRoot); } + /**获取请求成功的状态内容 * @return */ public static JSONObject newSuccessResult() { - return newSuccessResult(false); + return newSuccessResult(null); } + /**获取请求成功的状态内容 + * @param warn + * @return + */ + public static JSONObject newSuccessResult(String warn) { + return newSuccessResult(warn, false); + } + + /**获取请求成功的状态内容 + * @param warn * @param isRoot * @return */ - public static JSONObject newSuccessResult(boolean isRoot) { - return newResult(JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, isRoot); + public static JSONObject newSuccessResult(String warn, boolean isRoot) { + return newResult(JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, warn, isRoot); } - + /**添加请求成功的状态内容 * @param object * @param e - * @param isRoot * @return */ - public static JSONObject extendErrorResult(JSONObject object, Exception e) { + public static JSONObject extendErrorResult(JSONObject object, Throwable e) { return extendErrorResult(object, e, false); } /**添加请求成功的状态内容 @@ -774,84 +897,89 @@ public static JSONObject extendErrorResult(JSONObject object, Exception e) { * @param isRoot * @return */ - public static JSONObject extendErrorResult(JSONObject object, Exception e, boolean isRoot) { + public static JSONObject extendErrorResult(JSONObject object, Throwable e, boolean isRoot) { return extendErrorResult(object, e, null, null, isRoot); } /**添加请求成功的状态内容 * @param object * @return */ - public static JSONObject extendErrorResult(JSONObject object, Exception e, RequestMethod requestMethod, String url, boolean isRoot) { - String msg = e.getMessage(); - - if (Log.DEBUG && isRoot) { - try { - int index = msg.lastIndexOf(Log.KEY_SYSTEM_INFO_DIVIDER); - String info = index >= 0 ? msg.substring(index + Log.KEY_SYSTEM_INFO_DIVIDER.length()).trim() - : " \n **环境信息** " - + " \n 系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") - + " \n 数据库: " - + " \n JDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") - + " \n APIJSON: " + Log.VERSION; - - msg = index < 0 ? msg : msg.substring(0, index).trim(); - String encodedMsg = URLEncoder.encode(msg, "UTF-8"); - - 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 += Log.KEY_SYSTEM_INFO_DIVIDER + " \n | \n 浏览器打开以下链接查看解答" - + (isSQLException ? "" : apiatuoAndGitHubLink) - // GitHub Issue 搜索貌似是精准包含,不易找到答案 + " \n\nGitHub: \n https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+" + encodedMsg - + " \n\n【Google】:\n https://www.google.com/search?q=" + encodedMsg - + " \n\n【百度】:\n https://www.baidu.com/s?ie=UTF-8&wd=" + encodedMsg - + (isSQLException ? apiatuoAndGitHubLink : "") - + " \n\n都没找到答案?打开这个链接 \n https://github.com/Tencent/APIJSON/issues/new?assignees=&labels=&template=--bug.md " - + " \n然后提交问题,推荐用以下模板修改,注意要换行保持清晰可读。" - + " \n【标题】:" + msg - + " \n【内容】:" + info + "\n\n**问题描述**\n" + msg - + " \n\n" - + " \n\nPOST " + url - + " \n请求 Request JSON:\n ```js" - + " \n 请填写,例如 { \"Users\":{} }" - + " \n```" - + " \n\n返回结果 Response JSON:\n ```js" - + " \n 请填写,例如 { \"Users\": {}, \"code\": 401, \"msg\": \"Users 不允许 UNKNOWN 用户的 GET 请求!\" }" - + " \n```"; - } catch (Throwable e2) {} - } - - JSONObject error = newErrorResult(e, isRoot); - return extendResult(object, error.getIntValue(JSONResponse.KEY_CODE), msg, isRoot); - } + public static JSONObject extendErrorResult(JSONObject object, Throwable e, RequestMethod requestMethod, String url, boolean isRoot) { + String msg = CommonException.getMsg(e); + + if (Log.DEBUG && isRoot) { + try { + boolean isCommon = e instanceof CommonException; + String env = isCommon ? ((CommonException) e).getEnvironment() : null; + if (StringUtil.isEmpty(env)) { + //int index = msg.lastIndexOf(Log.KEY_SYSTEM_INFO_DIVIDER); + //env = index >= 0 ? msg.substring(index + Log.KEY_SYSTEM_INFO_DIVIDER.length()).trim() + env = " \n **环境信息** " + + " \n 系统: " + Log.OS_NAME + " " + Log.OS_VERSION + + " \n 数据库: " + + " \n JDK: " + Log.JAVA_VERSION + " " + Log.OS_ARCH + + " \n APIJSON: " + 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) {} + + Throwable t = isCommon ? e.getCause() : e; + boolean isSQLException = t instanceof SQLException; // SQL 报错一般都是通用问题,优先搜索引擎 + String apiatuoAndGitHubLink = "\n\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 += Log.KEY_SYSTEM_INFO_DIVIDER + " 浏览器打开以下链接查看解答" + + (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【内容】:" + env + "\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) {} + } + + int code = CommonException.getCode(e); + return extendResult(object, code, msg, null, isRoot); + } /**新建错误状态内容 * @param e @@ -867,71 +995,26 @@ public static JSONObject newErrorResult(Exception e) { */ public static JSONObject newErrorResult(Exception e, boolean isRoot) { if (e != null) { - e.printStackTrace(); + // if (Log.DEBUG) { + e.printStackTrace(); + // } - int code; - if (e instanceof UnsupportedEncodingException) { - code = JSONResponse.CODE_UNSUPPORTED_ENCODING; - } - else if (e instanceof IllegalAccessException) { - code = JSONResponse.CODE_ILLEGAL_ACCESS; - } - else if (e instanceof UnsupportedOperationException) { - code = JSONResponse.CODE_UNSUPPORTED_OPERATION; - } - else if (e instanceof NotExistException) { - code = JSONResponse.CODE_NOT_FOUND; - } - else if (e instanceof IllegalArgumentException) { - code = JSONResponse.CODE_ILLEGAL_ARGUMENT; - } - else if (e instanceof NotLoggedInException) { - code = JSONResponse.CODE_NOT_LOGGED_IN; - } - else if (e instanceof TimeoutException) { - code = JSONResponse.CODE_TIME_OUT; - } - else if (e instanceof ConflictException) { - code = JSONResponse.CODE_CONFLICT; - } - else if (e instanceof ConditionErrorException) { - code = JSONResponse.CODE_CONDITION_ERROR; - } - else if (e instanceof UnsupportedDataTypeException) { - code = JSONResponse.CODE_UNSUPPORTED_TYPE; - } - else if (e instanceof OutOfRangeException) { - code = JSONResponse.CODE_OUT_OF_RANGE; - } - else if (e instanceof NullPointerException) { - code = JSONResponse.CODE_NULL_POINTER; - } - else { - code = JSONResponse.CODE_SERVER_ERROR; - } + String msg = CommonException.getMsg(e); + Integer code = CommonException.getCode(e); - return newResult(code, e.getMessage(), isRoot); + return newResult(code, msg, null, isRoot); } - return newResult(JSONResponse.CODE_SERVER_ERROR, JSONResponse.MSG_SERVER_ERROR, isRoot); + return newResult(JSONResponse.CODE_SERVER_ERROR, JSONResponse.MSG_SERVER_ERROR, null, isRoot); } - - - //TODO 启动时一次性加载Request所有内容,作为初始化。 /**获取正确的请求,非GET请求必须是服务器指定的 - * @param method - * @param request * @return - * @throws Exception + * @throws Exception */ @Override public JSONObject parseCorrectRequest() throws Exception { - setTag(requestObject.getString(JSONRequest.KEY_TAG)); - setVersion(requestObject.getIntValue(JSONRequest.KEY_VERSION)); - requestObject.remove(JSONRequest.KEY_TAG); - requestObject.remove(JSONRequest.KEY_VERSION); return parseCorrectRequest(requestMethod, tag, version, "", requestObject, getMaxUpdateCount(), this); } @@ -946,7 +1029,6 @@ public JSONObject parseCorrectRequest() throws Exception { */ @Override public JSONObject getStructure(@NotNull String table, String method, String tag, int version) throws Exception { - // TODO 目前只使用 Request 而不使用 Response,所以这里写死用 REQUEST_MAP,以后可能 Response 表也会与 Request 表合并,用字段来区分 String cacheKey = AbstractVerifier.getCacheKeyForRequest(method, tag); SortedMap versionedMap = AbstractVerifier.REQUEST_MAP.get(cacheKey); @@ -994,8 +1076,8 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, return null; // 已使用 REQUEST_MAP 缓存全部,但没查到 } - //获取指定的JSON结构 <<<<<<<<<<<<<< - SQLConfig config = createSQLConfig().setMethod(GET).setTable(table); + // 获取指定的JSON结构 <<<<<<<<<<<<<< + SQLConfig config = createSQLConfig().setMethod(GET).setTable(table); config.setPrepared(false); config.setColumn(Arrays.asList("structure")); @@ -1010,7 +1092,7 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, config.setOrder(JSONRequest.KEY_VERSION + (version > 0 ? "+" : "-")); config.setCount(1); - //too many connections error: 不try-catch,可以让客户端看到是服务器内部异常 + // too many connections error: 不try-catch,可以让客户端看到是服务器内部异常 result = getSQLExecutor().execute(config, false); // version, method, tag 组合情况太多了,JDK 里又没有 LRUCache,所以要么启动时一次性缓存全部后面只用缓存,要么每次都查数据库 @@ -1025,18 +1107,20 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, protected Map arrayObjectParserCacheMap = new HashMap<>(); - // protected SQLConfig itemConfig; + // protected SQLConfig itemConfig; /**获取单个对象,该对象处于parentObject内 - * @param parentPath parentObject的路径 - * @param name parentObject的key - * @param request parentObject的value - * @param config for array item + * @param request parentObject 的 value + * @param parentPath parentObject 的路径 + * @param name parentObject 的 key + * @param arrayConfig config for array item + * @param isSubquery 是否为子查询 + * @param cache SQL 结果缓存 * @return - * @throws Exception + * @throws Exception */ @Override - public JSONObject onObjectParse(final JSONObject request - , String parentPath, String name, final SQLConfig arrayConfig, boolean isSubquery) throws Exception { + public JSONObject onObjectParse(final JSONObject request, String parentPath, String name + , final SQLConfig arrayConfig, boolean isSubquery, JSONObject cache) throws Exception { if (Log.DEBUG) { Log.i(TAG, "\ngetObject: parentPath = " + parentPath @@ -1069,7 +1153,7 @@ public JSONObject onObjectParse(final JSONObject request 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 = null; + ObjectParser op = null; if (isReuse) { // 数组主表使用专门的缓存数据 op = arrayObjectParserCacheMap.get(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2)); op.setParentPath(parentPath); @@ -1078,6 +1162,10 @@ public JSONObject onObjectParse(final JSONObject request if (op == null) { op = createObjectParser(request, parentPath, arrayConfig, isSubquery, isTable, isArrayMainTable); } + // 对象 - 设置 method + setOpMethod(request, op, name); + + op.setCache(cache); op = op.parse(name, isReuse); JSONObject response = null; @@ -1093,13 +1181,13 @@ 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) { // 解决对聚合函数字段通过 query:2 分页查总数返回值错误 // 这里可能改变了内部的一些数据,下方通过 arrayConfig 还原 - SQLConfig cfg = op.setSQLConfig(0, 0, 0).getSQLConfig(); + SQLConfig cfg = op.setSQLConfig(0, 0, 0).getSQLConfig(); boolean isExplain = cfg.isExplain(); cfg.setExplain(false); @@ -1107,7 +1195,7 @@ public JSONObject onObjectParse(final JSONObject request subqy.setFrom(cfg.getTable()); subqy.setConfig(cfg); - SQLConfig countSQLCfg = createSQLConfig(); + SQLConfig countSQLCfg = createSQLConfig(); countSQLCfg.setColumn(Arrays.asList("count(*):count")); countSQLCfg.setFrom(subqy); @@ -1118,7 +1206,7 @@ public JSONObject onObjectParse(final JSONObject request else { // 对聚合函数字段通过 query:2 分页查总数返回值错误 RequestMethod method = op.getMethod(); - rp = op.setMethod(RequestMethod.HEAD).setSQLConfig().executeSQL().getSqlReponse(); + rp = op.setMethod(RequestMethod.HEAD).setSQLConfig().executeSQL().getSqlResponse(); op.setMethod(method); } @@ -1137,23 +1225,28 @@ public JSONObject onObjectParse(final JSONObject request if (max < 0) { max = 0; } + int min = getMinQueryPage(); + + page += min; + max += min; 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); pagination.put(JSONResponse.KEY_MAX, max); pagination.put(JSONResponse.KEY_MORE, page < max); - pagination.put(JSONResponse.KEY_FIRST, page == 0); + pagination.put(JSONResponse.KEY_FIRST, page == min); pagination.put(JSONResponse.KEY_LAST, page == max); putQueryResult(pathPrefix + JSONResponse.KEY_INFO, pagination); - if (total <= count*page) { + if (total <= count*(page - min)) { query = JSONRequest.QUERY_TOTAL;//数量不够了,不再往后查询 } } @@ -1189,21 +1282,24 @@ public JSONObject onObjectParse(final JSONObject request } /**获取对象数组,该对象数组处于parentObject内 + * @param request parentObject的value * @param parentPath parentObject的路径 * @param name parentObject的key - * @param request parentObject的value - * @return + * @param isSubquery 是否为子查询 + * @param cache SQL 结果缓存 + * @return * @throws Exception */ @Override - public JSONArray onArrayParse(JSONObject request, String parentPath, String name, boolean isSubquery) throws Exception { + public JSONArray onArrayParse(JSONObject request, String parentPath, String name, boolean isSubquery, JSONArray cache) throws Exception { if (Log.DEBUG) { Log.i(TAG, "\n\n\n onArrayParse parentPath = " + parentPath + "; name = " + name + "; request = " + JSON.toJSONString(request)); } //不能允许GETS,否则会被通过"[]":{"@role":"ADMIN"},"Table":{},"tag":"Table"绕过权限并能批量查询 - if (isSubquery == false && RequestMethod.isGetMethod(requestMethod, true) == false) { + RequestMethod _method = request.get(apijson.JSONObject.KEY_METHOD) == null ? requestMethod : RequestMethod.valueOf(request.getString(apijson.JSONObject.KEY_METHOD)); + if (isSubquery == false && RequestMethod.isGetMethod(_method, true) == false) { throw new UnsupportedOperationException("key[]:{} 只支持 GET, GETS 方法!其它方法不允许传 " + name + ":{} 等这种 key[]:{} 格式!"); } if (request == null || request.isEmpty()) { // jsonKey-jsonValue 条件 @@ -1238,14 +1334,16 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name query2 = JSONRequest.QUERY_ALL; break; default: - throw new IllegalArgumentException(path + "/" + JSONRequest.KEY_QUERY + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [TABLE, TOTAL, ALL] 内 !"); + throw new IllegalArgumentException(path + "/" + JSONRequest.KEY_QUERY + ":value 中 value 的值不合法!必须在 [0, 1, 2] 或 [TABLE, TOTAL, ALL] 内 !"); } } - int page2 = page == null ? 0 : page; + int minPage = getMinQueryPage(); // 兼容各种传 0 或 null/undefined 自动转 0 导致的问题 + int page2 = page == null || page == 0 ? 0 : page - minPage; + int maxPage = getMaxQueryPage(); if (page2 < 0 || page2 > maxPage) { - throw new IllegalArgumentException(path + "/" + JSONRequest.KEY_PAGE + ":value 中 value 的值不合法!必须在 0-" + maxPage + " 内 !"); + throw new IllegalArgumentException(path + "/" + JSONRequest.KEY_PAGE + ":value 中 value 的值不合法!必须在 " + minPage + "-" + maxPage + " 内 !"); } //不用total限制数量了,只用中断机制,total只在query = 1,2的时候才获取 @@ -1283,30 +1381,32 @@ 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]; } //Table<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - response = new JSONArray(); - SQLConfig config = createSQLConfig() + + List joinList = onJoinParse(join, request); + SQLConfig config = createSQLConfig() .setMethod(requestMethod) .setCount(size) .setPage(page2) .setQuery(query2) .setCompat(compat) .setTable(arrTableKey) - .setJoinList(onJoinParse(join, request)); + .setJoinList(joinList); JSONObject parent; boolean isExtract = true; + response = new JSONArray(); //生成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); + parent = onObjectParse(request, isSubquery ? parentPath : path, isSubquery ? name : "" + i, config.setType(SQLConfig.TYPE_ITEM).setPosition(i), isSubquery, null); if (parent == null || parent.isEmpty()) { break; } @@ -1321,7 +1421,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // @SuppressWarnings("unchecked") List list = fo == null ? null : (List) fo.remove(AbstractSQLExecutor.KEY_RAW_LIST); - if (list != null && list.isEmpty() == false) { + if (list != null && list.isEmpty() == false && (joinList == null || joinList.isEmpty())) { isExtract = false; list.set(0, fo); // 不知道为啥第 0 项也加了 @RAW@LIST @@ -1390,6 +1490,8 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // 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_NAMESPACE); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_CATALOG); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_SCHEMA); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATASOURCE); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COLUMN); @@ -1400,14 +1502,15 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // 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_KEY); 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 +1543,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 table = entry.getKey(); // User if (StringUtil.isName(table) == false) { @@ -1476,16 +1588,33 @@ else if (join != null){ // 取出Table对应的JSONObject,及内部引用赋值 key:value JSONObject tableObj; + JSONObject parentPathObj; // 保留 try { - tableObj = request.getJSONObject(tableKey); + parentPathObj = arrKey == null ? request : request.getJSONObject(arrKey); // 保留 + tableObj = parentPathObj == null ? null : parentPathObj.getJSONObject(tableKey); if (tableObj == null) { throw new NullPointerException("tableObj == null"); } } catch (Exception e2) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + tableKey + ":value 中 value 类型不合法!必须是 {} 这种 JSONObject 格式!" + e2.getMessage()); + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + tableKey + ":value 中 value 类型不合法!" + + "必须是 {} 这种 JSONObject 格式!" + e2.getMessage()); } - + + if (arrKey != null) { + if (parentPathObj.get(JSONRequest.KEY_JOIN) != null) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + ":{ join: value } 中 value 不合法!" + + "@ APP JOIN 最多允许跨 1 层,只能是子数组,且数组对象中不能有 join: value 键值对!"); + } + + Integer subPage = parentPathObj.getInteger(JSONRequest.KEY_PAGE); + if (subPage != null && subPage != 0) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + ":{ page: value } 中 value 不合法!" + + "@ APP JOIN 最多允许跨 1 层,只能是子数组,且数组对象中 page 值只能为 null 或 0 !"); + } + } + + boolean isAppJoin = "@".equals(joinType); JSONObject refObj = new JSONObject(tableObj.size(), true); @@ -1496,15 +1625,21 @@ else if (join != null){ + "必须为 &/Table0,> tableSet = tableObj.entrySet(); // 取出所有 join 条件 JSONObject requestObj = new JSONObject(true); // (JSONObject) obj.clone(); @@ -1533,6 +1668,18 @@ 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) { + 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; } @@ -1580,6 +1727,10 @@ else if (join != null){ j.setAlias(alias); j.setOuter((JSONObject) outer); j.setRequest(requestObj); + if (arrKey != null) { + Integer count = parentPathObj.getInteger(JSONRequest.KEY_COUNT); + j.setCount(count == null ? getDefaultQueryCount() : count); + } List onList = new ArrayList<>(); for (Entry refEntry : refSet) { @@ -1590,7 +1741,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) { @@ -1613,7 +1764,7 @@ else if (join != null){ throw new IllegalArgumentException(e.getKey() + ":'/targetTable:targetAlias/targetKey' 中 targetAlias 值 " + targetAlias + " 不合法!必须满足英文单词变量名格式!"); } - targetTable = targetTableKey; // 主表允许别名 + //targetTable = targetTableKey; // 主表允许别名 if (StringUtil.isName(targetTable) == false) { throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中 targetTable 值 " + targetTable + " 不合法!必须满足大写字母开头的表对象英文单词 key 格式!"); } @@ -1630,33 +1781,34 @@ 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.setTargetTableKey(targetTableKey); 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)); + // 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); + parentPathObj.put(tableKey, refObj); // tableObj.clear(); // tableObj.putAll(refObj); @@ -1664,8 +1816,8 @@ else if (join != null){ // 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 >>>>>>>>> } - //拼接多个 SQLConfig 的SQL语句,然后执行,再把结果分别缓存(Moment, User等)到 SQLExecutor 的 cacheMap - // AbstractSQLConfig config0 = null; + //拼接多个 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(); @@ -1677,22 +1829,24 @@ else if (join != null){ * @param pathKeys * @return */ - protected static Object getValue(JSONObject parent, String[] pathKeys) { + public static V getValue(JSONObject parent, String[] pathKeys) { if (parent == null || pathKeys == null || pathKeys.length <= 0) { Log.w(TAG, "getChild parent == null || pathKeys == null || pathKeys.length <= 0 >> return parent;"); - return parent; + return (V) parent; } //逐层到达child的直接容器JSONObject parent - final int last = pathKeys.length - 1; + int last = pathKeys.length - 1; for (int i = 0; i < last; i++) {//一步一步到达指定位置 if (parent == null) {//不存在或路径错误(中间的key对应value不是JSONObject) break; } - parent = getJSONObject(parent, pathKeys[i]); + + String k = getDecodedKey(pathKeys[i]); + parent = getJSONObject(parent, k); } - return parent == null ? null : parent.get(pathKeys[last]); + return parent == null ? null : (V) parent.get(getDecodedKey(pathKeys[last])); } @@ -1754,7 +1908,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]; } @@ -1818,18 +1972,21 @@ public Object getValueByPath(String valuePath) { } //逐层到达targetKey的直接容器JSONObject parent - if (keys != null && keys.length > 1) { - for (int i = 0; i < keys.length - 1; i++) {//一步一步到达指定位置parentPath + int last = keys == null ? -1 : keys.length - 1; + if (last >= 1) { + for (int i = 0; i < last; i++) {//一步一步到达指定位置parentPath if (parent == null) {//不存在或路径错误(中间的key对应value不是JSONObject) break; } - parent = getJSONObject(parent, keys[i]); + + String k = getDecodedKey(keys[i]); + parent = getJSONObject(parent, k); } } if (parent != null) { Log.i(TAG, "getValueByPath >> get from queryResultMap >> return parent.get(keys[keys.length - 1]);"); - target = keys == null || keys.length <= 0 ? parent : parent.get(keys[keys.length - 1]); //值为null应该报错NotExistExeption,一般都是id关联,不可为null,否则可能绕过安全机制 + target = last < 0 ? parent : parent.get(getDecodedKey(keys[last])); //值为null应该报错NotExistExeption,一般都是id关联,不可为null,否则可能绕过安全机制 if (target != null) { Log.i(TAG, "getValueByPath >> getValue >> return target = " + target); return target; @@ -1844,8 +2001,20 @@ public Object getValueByPath(String valuePath) { return target; } - Log.i(TAG, "getValueByPath return valuePath;"); - return valuePath; + Log.i(TAG, "getValueByPath return null;"); + return null; + } + + /**解码 引用赋值 路径中的 key,支持把 URL encode 后的值,转为 decode 后的原始值,例如 %2Fuser%2Flist -> /user/list ; %7B%7D -> [] + * @param key + * @return + */ + public static String getDecodedKey(String key) { + try { + return URLDecoder.decode(key, StringUtil.UTF_8); + } catch (Throwable e) { + return key; + } } //依赖引用关系 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -1888,12 +2057,16 @@ public JSONObject getArrayMainCacheItem(String arrayPath, int position) { * @throws Exception */ @Override - public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Exception { + public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Exception { if (config == null) { Log.d(TAG, "executeSQL config == null >> return null;"); return null; } + config.setParser(this); + config.setVersion(getVersion()); + config.setTag(getTag()); + if (isSubquery) { JSONObject sqlObj = new JSONObject(true); sqlObj.put(KEY_CONFIG, config); @@ -1910,7 +2083,7 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except JSONObject res = getSQLExecutor().execute(config, false); //如果是查询方法,才能执行explain - if (RequestMethod.isQueryMethod(config.getMethod())){ + if (RequestMethod.isQueryMethod(config.getMethod()) && config.isElasticsearch() == false){ config.setExplain(explain); JSONObject explainResult = config.isMain() && config.getPosition() != 0 ? null : getSQLExecutor().execute(config, false); @@ -1932,57 +2105,14 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except else { sqlExecutor = getSQLExecutor(); result = sqlExecutor.execute(config, false); - // FIXME 改为直接在 sqlExecutor 内加好,最后 Parser 取结果,可以解决并发执行导致内部计算出错 + // FIXME 改为直接在 sqlExecutor 内加好,最后 Parser 取结果,可以解决并发执行导致内部计算出错 // executedSQLDuration += sqlExecutor.getExecutedSQLDuration() + sqlExecutor.getSqlResultDuration(); } return result; } catch (Exception e) { - String msg = e.getMessage(); - if (Log.DEBUG && msg != null && msg.contains(Log.KEY_SYSTEM_INFO_DIVIDER) == false) { - try { - String db = config.getDatabase(); - if (db == null) { - if (config.isMySQL()) { - db = SQLConfig.DATABASE_MYSQL; - } - else 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 = AbstractSQLConfig.DEFAULT_DATABASE; - } - } - - Class clazz = e.getClass(); - e = clazz.getConstructor(String.class).newInstance( - msg - + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n **环境信息** " - + " \n 系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") - + " \n 数据库: " + db + " " + config.getDBVersion() - + " \n JDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") - + " \n APIJSON: " + Log.VERSION - ); - } catch (Throwable e2) {} - } - - if (Log.DEBUG == false && e instanceof SQLException) { - throw new SQLException("数据库驱动执行异常SQLException,非 Log.DEBUG 模式下不显示详情,避免泄漏真实模式名、表名等隐私信息", e); - } - throw e; + throw CommonException.wrap(e, config); } finally { if (config.getPosition() == 0 && config.limitSQLCount()) { @@ -2011,7 +2141,7 @@ public void setTransactionIsolation(int transactionIsolation) { @Override public void begin(int transactionIsolation) { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<<<<<<<<<<< begin transactionIsolation = " + transactionIsolation + " >>>>>>>>>>>>>>>>>>>>>>> \n\n"); - getSQLExecutor().setTransactionIsolation(transactionIsolation); //不知道 connection 什么时候创建,不能在这里准确控制,getSqlExecutor().begin(transactionIsolation); + getSQLExecutor().setTransactionIsolation(transactionIsolation); // 不知道 connection 什么时候创建,不能在这里准确控制,getSqlExecutor().begin(transactionIsolation); } @Override public void rollback() throws SQLException { @@ -2048,7 +2178,9 @@ protected void onBegin() { */ protected void onCommit() { // Log.d(TAG, "onCommit >>"); - if (RequestMethod.isQueryMethod(requestMethod)) { + // this.sqlExecutor.getTransactionIsolation() 只有json第一次执行才会设置, get请求=0 + if (RequestMethod.isQueryMethod(requestMethod) + && getSQLExecutor().getTransactionIsolation() == Connection.TRANSACTION_NONE) { return; } @@ -2094,4 +2226,315 @@ protected void onClose() { queryResultMap = null; } + private void setOpMethod(JSONObject request, ObjectParser op, String key) { + String _method = key == null ? null : request.getString(apijson.JSONObject.KEY_METHOD); + if (_method != null) { + RequestMethod method = RequestMethod.valueOf(_method); // 必须精准匹配,避免缓存命中率低 + this.setMethod(method); + op.setMethod(method); + } + } + + protected JSONObject getRequestStructure(RequestMethod method, String tag, int version) throws Exception { + // 获取指定的JSON结构 <<<<<<<<<<<< + JSONObject object = null; + String error = ""; + try { + object = getStructure("Request", method.name(), tag, version); + } catch (Exception e) { + error = e.getMessage(); + } + if (object == null) { // empty表示随意操作 || object.isEmpty()) { + throw new UnsupportedOperationException("找不到 version: " + version + ", method: " + method.name() + ", tag: " + tag + " 对应的 structure !" + "非开放请求必须是后端 Request 表中校验规则允许的操作!\n " + error + "\n如果需要则在 Request 表中新增配置!"); + } + + return object; + } + + protected JSONObject batchVerify(RequestMethod method, String tag, int version, String name, @NotNull JSONObject request, int maxUpdateCount, SQLCreator creator) throws Exception { + JSONObject correctRequest = new JSONObject(true); + List removeTmpKeys = new ArrayList<>(); // 请求json里面的临时变量,不需要带入后面的业务中,比如 @post、@get等 + + Set reqSet = request == null ? null : request.keySet(); + if (reqSet == null || request.isEmpty()) { + throw new IllegalArgumentException("JSON 对象格式不正确 !正确示例例如 \"User\": {}"); + } + + for (String key : reqSet) { + // key 重复直接抛错(xxx:alias, xxx:alias[]) + if (correctRequest.containsKey(key) || correctRequest.containsKey(key + apijson.JSONObject.KEY_ARRAY)) { + throw new IllegalArgumentException("对象名重复,请添加别名区分 ! 重复对象名为: " + key); + } + + boolean isPost = apijson.orm.JSONRequest.KEY_POST.equals(key); + // @post、@get 等 RequestMethod + try { + RequestMethod keyMethod = isPost ? RequestMethod.POST : JSONRequest.KEY_METHOD_ENUM_MAP.get(key); + if (keyMethod != null) { + // 如果不匹配,异常不处理即可 + removeTmpKeys.add(key); + + Object val = request.get(key); + JSONObject obj = val instanceof JSONObject ? request.getJSONObject(key) : null; + if (obj == null) { + if (val instanceof String) { + String[] tbls = StringUtil.split((String) val); + if (tbls != null && tbls.length > 0) { + obj = new JSONObject(true); + for (int i = 0; i < tbls.length; i++) { + String tbl = tbls[i]; + if (obj.containsKey(tbl)) { + throw new ConflictException(key + ": value 中 " + tbl + " 已经存在,不能重复!"); + } + + obj.put(tbl, isPost && JSONRequest.isTableArray(tbl) + ? tbl.substring(0, tbl.length() - 2) + ":[]" : ""); + } + } + } + else { + throw new IllegalArgumentException(key + ": value 中 value 类型错误,只能是 String 或 JSONObject {} !"); + } + } + + Set> set = obj == null ? new HashSet<>() : obj.entrySet(); + + for (Entry objEntry : set) { + String objKey = objEntry == null ? null : objEntry.getKey(); + if (objKey == null) { + continue; + } + + Map objAttrMap = new HashMap<>(); + objAttrMap.put(apijson.JSONObject.KEY_METHOD, keyMethod); + keyObjectAttributesMap.put(objKey, objAttrMap); + + Object objVal = objEntry.getValue(); + JSONObject objAttrJson = objVal instanceof JSONObject ? obj.getJSONObject(objKey) : null; + if (objAttrJson == null) { + if (objVal instanceof String) { + objAttrMap.put(JSONRequest.KEY_TAG, "".equals(objVal) ? objKey : objVal); + } + else { + throw new IllegalArgumentException(key + ": { " + objKey + ": value 中 value 类型错误,只能是 String 或 JSONObject {} !"); + } + } + else { + Set> objSet = objAttrJson.entrySet(); + + boolean hasTag = false; + for (Entry entry : objSet) { + String objAttrKey = entry == null ? null : entry.getKey(); + if (objAttrKey == null) { + continue; + } + + switch (objAttrKey) { + case apijson.JSONObject.KEY_DATASOURCE: + case apijson.JSONObject.KEY_SCHEMA: + case apijson.JSONObject.KEY_DATABASE: + case JSONRequest.KEY_VERSION: + case apijson.JSONObject.KEY_ROLE: + objAttrMap.put(objAttrKey, entry.getValue()); + break; + case JSONRequest.KEY_TAG: + hasTag = true; + objAttrMap.put(objAttrKey, entry.getValue()); + break; + default: + break; + } + } + + if (hasTag == false) { + objAttrMap.put(JSONRequest.KEY_TAG, isPost && JSONRequest.isTableArray(objKey) + ? objKey.substring(0, objKey.length() - 2) + ":[]" : objKey); + } + } + } + continue; + } + + // 1、非crud,对于没有显式声明操作方法的,直接用 URL(/get, /post 等) 对应的默认操作方法 + // 2、crud, 没有声明就用 GET + // 3、兼容 sql@ JSONObject,设置 GET方法 + // 将method 设置到每个object, op执行会解析 + Object obj = request.get(key); + + if (obj instanceof JSONObject) { + Map attrMap = keyObjectAttributesMap.get(key); + + if (attrMap == null) { + // 数组会解析为对象进行校验,做一下兼容 + if (keyObjectAttributesMap.get(key + apijson.JSONObject.KEY_ARRAY) == null) { + if (method == RequestMethod.CRUD || key.endsWith("@")) { + ((JSONObject) obj).put(apijson.JSONObject.KEY_METHOD, GET); + Map objAttrMap = new HashMap<>(); + objAttrMap.put(apijson.JSONObject.KEY_METHOD, GET); + keyObjectAttributesMap.put(key, objAttrMap); + } else { + ((JSONObject) obj).put(apijson.JSONObject.KEY_METHOD, method); + Map objAttrMap = new HashMap<>(); + objAttrMap.put(apijson.JSONObject.KEY_METHOD, method); + keyObjectAttributesMap.put(key, objAttrMap); + } + } else { + setRequestAttribute(key, true, apijson.JSONObject.KEY_METHOD, request); + setRequestAttribute(key, true, apijson.JSONObject.KEY_DATASOURCE, request); + setRequestAttribute(key, true, apijson.JSONObject.KEY_SCHEMA, request); + setRequestAttribute(key, true, apijson.JSONObject.KEY_DATABASE, request); + setRequestAttribute(key, true, apijson.JSONObject.VERSION, request); + setRequestAttribute(key, true, apijson.JSONObject.KEY_ROLE, request); + } + } else { + setRequestAttribute(key, false, apijson.JSONObject.KEY_METHOD, request); + setRequestAttribute(key, false, apijson.JSONObject.KEY_DATASOURCE, request); + setRequestAttribute(key, false, apijson.JSONObject.KEY_SCHEMA, request); + setRequestAttribute(key, false, apijson.JSONObject.KEY_DATABASE, request); + setRequestAttribute(key, false, apijson.JSONObject.VERSION, request); + setRequestAttribute(key, false, apijson.JSONObject.KEY_ROLE, request); + } + } + + if (key.startsWith("@") || key.endsWith("@")) { + correctRequest.put(key, obj); + continue; + } + + if (obj instanceof JSONObject || obj instanceof JSONArray) { + RequestMethod _method; + if (obj instanceof JSONObject) { + JSONObject tblObj = request.getJSONObject(key); + String mn = tblObj == null ? null : tblObj.getString(apijson.JSONObject.KEY_METHOD); + _method = mn == null ? null : RequestMethod.valueOf(mn); + String combine = _method == null ? null : tblObj.getString(KEY_COMBINE); + if (combine != null && RequestMethod.isPublicMethod(_method) == false) { + throw new IllegalArgumentException(key + ":{} 里的 @combine:value 不合法!开放请求 GET、HEAD 才允许传 @combine:value !"); + } + } else { + Map attrMap = keyObjectAttributesMap.get(key); + + if (attrMap == null) { + if (method == RequestMethod.CRUD) { + _method = GET; + Map objAttrMap = new HashMap<>(); + objAttrMap.put(apijson.JSONObject.KEY_METHOD, GET); + keyObjectAttributesMap.put(key, objAttrMap); + } else { + _method = method; + Map objAttrMap = new HashMap<>(); + objAttrMap.put(apijson.JSONObject.KEY_METHOD, method); + keyObjectAttributesMap.put(key, objAttrMap); + } + } else { + _method = (RequestMethod) attrMap.get(apijson.JSONObject.KEY_METHOD); + } + } + + // 非 CRUD 方法,都只能和 URL method 完全一致,避免意料之外的安全风险。 + if (method != RequestMethod.CRUD && _method != method) { + throw new IllegalArgumentException("不支持在 " + method + " 中 " + _method + " !"); + } + + // get请求不校验 + if (RequestMethod.isPublicMethod(_method)) { + correctRequest.put(key, obj); + continue; + } + + if (tag != null && ! tag.contains(":")) { + JSONObject object = getRequestStructure(_method, tag, version); + JSONObject ret = objectVerify(_method, tag, version, name, request, maxUpdateCount, creator, object); + correctRequest.putAll(ret); + break; + } + + String _tag = buildTag(request, key, method, tag); + JSONObject object = getRequestStructure(_method, _tag, version); + if (method == RequestMethod.CRUD && StringUtil.isEmpty(tag, true)) { + JSONObject requestItem = new JSONObject(); + requestItem.put(key, obj); + JSONObject ret = objectVerify(_method, _tag, version, name, requestItem, maxUpdateCount, creator, object); + correctRequest.put(key, ret.get(key)); + } else { + return objectVerify(_method, _tag, version, name, request, maxUpdateCount, creator, object); + } + } else { + correctRequest.put(key, obj); + } + } catch (Exception e) { + e.printStackTrace(); + throw new Exception(e); // 包装一层只是为了打印日志?看起来没必要 + } + } + + // 这里是 requestObject ref request 的引用, 删除不需要的临时变量 + for (String removeKey : removeTmpKeys) { + request.remove(removeKey); + } + + return correctRequest; + } + + public static > E getEnum(final Class enumClass, final String enumName, final E defaultEnum) { + if (enumName == null) { + return defaultEnum; + } + try { + return Enum.valueOf(enumClass, enumName); + } catch (final IllegalArgumentException ex) { + return defaultEnum; + } + } + + protected void setRequestAttribute(String key, boolean isArray, String attrKey, @NotNull JSONObject request) { + Map attrMap = keyObjectAttributesMap.get(isArray ? key + apijson.JSONObject.KEY_ARRAY : key); + Object attrVal = attrMap == null ? null : attrMap.get(attrKey); + JSONObject obj = attrVal == null ? null : request.getJSONObject(key); + + if (obj != null && obj.get(attrKey) == null) { + // 如果对象内部已经包含该属性,不覆盖 + obj.put(attrKey, attrVal); + } + } + + protected String buildTag(JSONObject request, String key, RequestMethod method, String tag) { + if (method == RequestMethod.CRUD) { + Map attrMap = keyObjectAttributesMap.get(key); + Object _tag = attrMap == null ? null : attrMap.get(JSONRequest.KEY_TAG); + return _tag != null ? _tag.toString() : StringUtil.isEmpty(tag) ? key : tag; + } else { + if (StringUtil.isEmpty(tag, true)) { + throw new IllegalArgumentException("请在最外层传 tag !一般是 Table 名,例如 \"tag\": \"User\" "); + } + } + return tag; + } + + + protected JSONObject objectVerify(RequestMethod method, String tag, int version, String name, @NotNull JSONObject request + , int maxUpdateCount, SQLCreator creator, JSONObject object) throws Exception { + // 获取指定的JSON结构 >>>>>>>>>>>>>> + JSONObject target = wrapRequest(method, tag, object, true); + // JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} + return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobalDatabase(), getGlobalSchema(), creator); + } + + /*** + * 兼容url crud, 获取真实method + * @param method = crud + * @param key + * @return + */ + public RequestMethod getRealMethod(RequestMethod method, String key, Object value) { + if (method == CRUD && (value instanceof JSONObject || value instanceof JSONArray)) { + Map attrMap = keyObjectAttributesMap.get(key); + Object _method = attrMap == null ? null : attrMap.get(apijson.JSONObject.KEY_METHOD); + if (_method instanceof RequestMethod) { + return (RequestMethod) _method; + } + } + + return method; + } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index c9c9c8623..19446e69e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -5,53 +5,14 @@ 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_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; -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; -import static apijson.JSONObject.KEY_ORDER; -import static apijson.JSONObject.KEY_RAW; -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.SQL.AND; -import static apijson.SQL.NOT; -import static apijson.SQL.ON; -import static apijson.SQL.OR; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.regex.Pattern; - -import javax.activation.UnsupportedDataTypeException; - import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.annotation.JSONField; +import java.util.*; +import java.util.Map.Entry; +import java.util.regex.Pattern; + import apijson.JSON; import apijson.JSONResponse; import apijson.Log; @@ -61,7 +22,12 @@ import apijson.StringUtil; import apijson.orm.Join.On; import apijson.orm.exception.NotExistException; +import apijson.orm.exception.UnsupportedDataTypeException; import apijson.orm.model.Access; +import apijson.orm.model.AllColumn; +import apijson.orm.model.AllColumnComment; +import apijson.orm.model.AllTable; +import apijson.orm.model.AllTableComment; import apijson.orm.model.Column; import apijson.orm.model.Document; import apijson.orm.model.ExtendedProperty; @@ -74,10 +40,20 @@ import apijson.orm.model.Table; import apijson.orm.model.TestRecord; +import static apijson.JSONObject.*; +import static apijson.RequestMethod.DELETE; +import static apijson.RequestMethod.GET; +import static apijson.RequestMethod.POST; +import static apijson.RequestMethod.PUT; +import static apijson.SQL.AND; +import static apijson.SQL.NOT; +import static apijson.SQL.ON; +import static apijson.SQL.OR; + /**config sql for JSON Request * @author Lemon */ -public abstract class AbstractSQLConfig implements SQLConfig { +public abstract class AbstractSQLConfig implements SQLConfig { private static final String TAG = "AbstractSQLConfig"; /** @@ -91,25 +67,61 @@ public abstract class AbstractSQLConfig implements SQLConfig { */ public static boolean IS_HAVING_ALLOW_NOT_FUNCTION = false; + /** + * 开启 WITH AS 表达式(在支持这种语法的数据库及版本)来简化 SQL 和提升性能 + */ + public static boolean ENABLE_WITH_AS = false; + + /** + * 对指定的方法,忽略空字符串,不作为 GET 条件,PUT 值等。可取值 new RequestMethod[]{ RequestMethod.GET, RequestMethod.POST ... } + */ + public static List IGNORE_EMPTY_STRING_METHOD_LIST = null; + /** + * 对指定的方法,忽略空白字符串。即首尾 trim 去掉所有不可见字符后,仍然为空的,就忽略,不作为 GET 条件,PUT 值等。 + * 可取值 new RequestMethod[]{ RequestMethod.GET, RequestMethod.POST ... } + */ + public static List IGNORE_BLANK_STRING_METHOD_LIST = null; + + public static String KEY_DELETED_KEY = "deletedKey"; + public static String KEY_DELETED_VALUE = "deletedValue"; + public static String KEY_NOT_DELETED_VALUE = "notDeletedValue"; + 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 boolean ALLOW_MISSING_KEY_4_COMBINE = true; public static String DEFAULT_DATABASE = DATABASE_MYSQL; + public static String DEFAULT_NAMESPACE = ""; // "root"; + public static String DEFAULT_CATALOG = ""; // PostgreSQL JDBC 必须在 URI 中传 ""postgres"; public static String DEFAULT_SCHEMA = "sys"; - public static String PREFFIX_DISTINCT = "DISTINCT "; + public static String PREFIX_DISTINCT = "DISTINCT "; + public static Pattern PATTERN_SCHEMA; // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! - private static Pattern PATTERN_RANGE; - private static Pattern PATTERN_FUNCTION; + public static Pattern PATTERN_RANGE; + public static Pattern PATTERN_FUNCTION; + + /** + * 表 SCHEMA 映射 + */ + public static Map TABLE_SCHEMA_MAP; /** * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 */ public static Map TABLE_KEY_MAP; + /** + * 字段名映射,隐藏真实字段名,对安全要求很高的表可以这么做,另外可以配置 name_tag:(name,tag) 来实现多字段 IN,length_tag:length(tag) 来实现 SQL 函数复杂条件 + */ + public static Map COLUMN_KEY_MAP; + /** + * 允许批量增删改部分记录失败的表 + */ + public static Map ALLOW_PARTIAL_UPDATE_FAIL_TABLE_MAP; public static List CONFIG_TABLE_LIST; public static List DATABASE_LIST; @@ -120,10 +132,25 @@ public abstract class AbstractSQLConfig implements SQLConfig { public static Map SQL_FUNCTION_MAP; static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- /**/ ,以免拼接 SQL 时被注入意外可执行指令 + PATTERN_SCHEMA = Pattern.compile("^[A-Za-z0-9_-]+$"); 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(); + // TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 + PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~`!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\(\\)\\$]+$"); + + TABLE_SCHEMA_MAP = new HashMap<>(); + TABLE_SCHEMA_MAP.put(Table.class.getSimpleName(), DEFAULT_SCHEMA); + TABLE_SCHEMA_MAP.put(Column.class.getSimpleName(), DEFAULT_SCHEMA); + TABLE_SCHEMA_MAP.put(PgClass.class.getSimpleName(), DEFAULT_SCHEMA); + TABLE_SCHEMA_MAP.put(PgAttribute.class.getSimpleName(), DEFAULT_SCHEMA); + TABLE_SCHEMA_MAP.put(SysTable.class.getSimpleName(), DEFAULT_SCHEMA); + TABLE_SCHEMA_MAP.put(SysColumn.class.getSimpleName(), DEFAULT_SCHEMA); + TABLE_SCHEMA_MAP.put(ExtendedProperty.class.getSimpleName(), DEFAULT_SCHEMA); + TABLE_SCHEMA_MAP.put(AllTable.class.getSimpleName(), DEFAULT_SCHEMA); + TABLE_SCHEMA_MAP.put(AllColumn.class.getSimpleName(), DEFAULT_SCHEMA); + TABLE_SCHEMA_MAP.put(AllTableComment.class.getSimpleName(), DEFAULT_SCHEMA); + TABLE_SCHEMA_MAP.put(AllColumnComment.class.getSimpleName(), DEFAULT_SCHEMA); + + 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); @@ -131,6 +158,12 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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); + TABLE_KEY_MAP.put(AllTable.class.getSimpleName(), AllTable.TABLE_NAME); + TABLE_KEY_MAP.put(AllColumn.class.getSimpleName(), AllColumn.TABLE_NAME); + TABLE_KEY_MAP.put(AllTableComment.class.getSimpleName(), AllTableComment.TABLE_NAME); + TABLE_KEY_MAP.put(AllColumnComment.class.getSimpleName(), AllColumnComment.TABLE_NAME); + + ALLOW_PARTIAL_UPDATE_FAIL_TABLE_MAP = new HashMap<>(); CONFIG_TABLE_LIST = new ArrayList<>(); // Table, Column 等是系统表 AbstractVerifier.SYSTEM_ACCESS_MAP.keySet()); CONFIG_TABLE_LIST.add(Function.class.getSimpleName()); @@ -146,8 +179,32 @@ 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_MARIADB); + DATABASE_LIST.add(DATABASE_TIDB); + DATABASE_LIST.add(DATABASE_COCKROACHDB); + DATABASE_LIST.add(DATABASE_DAMENG); + DATABASE_LIST.add(DATABASE_KINGBASE); + DATABASE_LIST.add(DATABASE_ELASTICSEARCH); + DATABASE_LIST.add(DATABASE_MANTICORE); DATABASE_LIST.add(DATABASE_CLICKHOUSE); DATABASE_LIST.add(DATABASE_HIVE); + DATABASE_LIST.add(DATABASE_PRESTO); + DATABASE_LIST.add(DATABASE_TRINO); + DATABASE_LIST.add(DATABASE_MILVUS); + DATABASE_LIST.add(DATABASE_INFLUXDB); + DATABASE_LIST.add(DATABASE_TDENGINE); + DATABASE_LIST.add(DATABASE_TIMESCALEDB); + DATABASE_LIST.add(DATABASE_IOTDB); + DATABASE_LIST.add(DATABASE_SNOWFLAKE); + DATABASE_LIST.add(DATABASE_DATABRICKS); + DATABASE_LIST.add(DATABASE_REDIS); + DATABASE_LIST.add(DATABASE_MONGODB); + DATABASE_LIST.add(DATABASE_CASSANDRA); + DATABASE_LIST.add(DATABASE_KAFKA); + DATABASE_LIST.add(DATABASE_MQ); + DATABASE_LIST.add(DATABASE_DUCKDB); + DATABASE_LIST.add(DATABASE_SURREALDB); + DATABASE_LIST.add(DATABASE_OPENGAUSS); RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 @@ -166,6 +223,16 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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", ""); RAW_MAP.put("IS NOT NULL", ""); @@ -213,7 +280,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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", ""); @@ -229,383 +295,386 @@ public abstract class AbstractSQLConfig implements SQLConfig { //窗口函数关键字 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("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("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("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_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<>(); // 保证顺序,避免配置冲突等意外情况 - - //窗口函数 - 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) + SQL_AGGREGATE_FUNCTION_MAP.put("max", ""); // MAX(a, b, c ...) 最大值 + SQL_AGGREGATE_FUNCTION_MAP.put("min", ""); // MIN(a, b, c ...) 最小值 + SQL_AGGREGATE_FUNCTION_MAP.put("avg", ""); // AVG(a, b, c ...) 平均值 + SQL_AGGREGATE_FUNCTION_MAP.put("count", ""); // COUNT(a, b, c ...) 总数 + SQL_AGGREGATE_FUNCTION_MAP.put("sum", ""); // SUM(a, b, c ...) 总和 + + SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + // 窗口函数 + SQL_FUNCTION_MAP.put("rank", ""); // RANK(a, b, c ...) 得到数据项在分组中的排名,排名相等的时候会留下空位 + SQL_FUNCTION_MAP.put("dense_rank", ""); // DENSE_RANK(a, b, c ...) 得到数据项在分组中的排名,排名相等的时候不会留下空位 + SQL_FUNCTION_MAP.put("row_num", ""); // ROW_NUM() 按照分组中的顺序生成序列,不存在重复的序列 + SQL_FUNCTION_MAP.put("row_number", ""); // ROW_NUMBER() 按照分组中的顺序生成序列,不存在重复的序列 + SQL_FUNCTION_MAP.put("ntile", ""); // NTILE(a, b, c ...) 用于将分组数据按照顺序切分成N片,返回当前切片值,不支持ROWS_BETWEE + SQL_FUNCTION_MAP.put("first_value", ""); // FIRST_VALUE() 取分组排序后,截止到当前行,分组内第一个值 + SQL_FUNCTION_MAP.put("last_value", ""); // LAST_VALUE() 取分组排序后,截止到当前行,分组内的最后一个值 + SQL_FUNCTION_MAP.put("lag", ""); // LAG() 统计窗口内往上第n行值。第一个参数为列名,第二个参数为往上第n行(可选,默认为1),第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL) + SQL_FUNCTION_MAP.put("lead", ""); // LEAD() 统计窗口内往下第n行值。第一个参数为列名,第二个参数为往下第n行(可选,默认为1),第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL) + SQL_FUNCTION_MAP.put("cume_dist", ""); // CUME_DIST() 返回(小于等于当前行值的行数)/(当前分组内的总行数) + SQL_FUNCTION_MAP.put("percent_rank", ""); // PERCENT_RANK(a, b, c ...) 返回(组内当前行的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,如果 s1s2 返回 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互为反函数 + SQL_FUNCTION_MAP.put("adddate", ""); // ADDDATE(d,n) 计算起始日期 d 加上 n 天的日期 + SQL_FUNCTION_MAP.put("addtime", ""); // ADDTIME(t,n) n 是一个时间表达式,时间 t 加上时间表达式 n + SQL_FUNCTION_MAP.put("curdate", ""); // CURDATE() 返回当前日期 + SQL_FUNCTION_MAP.put("current_date", ""); // CURRENT_DATE() 返回当前日期 + SQL_FUNCTION_MAP.put("current_time", ""); // CURRENT_TIME 返回当前时间 + SQL_FUNCTION_MAP.put("current_timestamp", ""); // CURRENT_TIMESTAMP() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("curtime", ""); // CURTIME() 返回当前时间 + SQL_FUNCTION_MAP.put("date", ""); // DATE() 从日期或日期时间表达式中提取日期值 + SQL_FUNCTION_MAP.put("datediff", ""); // DATEDIFF(d1,d2) 计算日期 d1->d2 之间相隔的天数 + 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(sec, format); // ) 将以秒为单位的时间 s 转换为时分秒的格式 + SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE(string, format) 将字符串转变为日期 + 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 对象 + 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_array_get", ""); // JSON_ARRAY_GET(json_doc, position) 从JSON数组提取指定位置的元素 + SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象 + SQL_FUNCTION_MAP.put("json_array_contains", ""); // JSON_ARRAY_CONTAINS(json_doc, path) 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_extract_scalar", ""); // JSON_EXTRACT_SCALAR(json_doc, path) 从JSON文档返回基础类型数据,例如 Boolean, Number, String + 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_size", ""); // JSON_SIZE(json_doc) JSON文档中的元素数 + SQL_FUNCTION_MAP.put("json_array_length", ""); // JSON_ARRAY_LENGTH(json_doc) JSON文档中的元素数 + SQL_FUNCTION_MAP.put("json_format", ""); // JSON_FORMAT(json_doc) 格式化 JSON + SQL_FUNCTION_MAP.put("json_parse", ""); // JSON_PARSE(val) 转换为 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 对象 + SQL_FUNCTION_MAP.put("is_json_scalar", ""); // IS_JSON_SCALAR(val)) 是否为JSON基本类型,例如 Boolean, Number, String // 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) 全文检索 - SQL_FUNCTION_MAP.put("any_value", ""); // any_value(userId) 解决 ONLY_FULL_GROUP_BY 报错 - - - - - - //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("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) 全文检索 + SQL_FUNCTION_MAP.put("any_value", ""); // any_value(userId) 解决 ONLY_FULL_GROUP_BY 报错 + + + // 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("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("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("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("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("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("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("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("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("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("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("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类型 + // 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("toInt8OrZero", ""); // toInt(8|16|32|64)OrZero 尝试把字符串转为 Int 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("toInt8OrNull", "");// toInt(8|16|32|64)O 尝试把字符串转为 Int 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("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("toUInt8OrZero", ""); // toInt(8|16|32|64)OrZero 尝试把字符串转为 Int 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("toUInt8OrNull", ""); // toInt(8|16|32|64)OrNull 尝试把字符串转为 Int SQL_FUNCTION_MAP.put("toUInt16OrNull", ""); SQL_FUNCTION_MAP.put("toUInt32OrNull", ""); SQL_FUNCTION_MAP.put("toUInt64OrNull", ""); @@ -618,14 +687,14 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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("toDateOrZero", ""); // toInt16(expr) + SQL_FUNCTION_MAP.put("toDateOrNull", ""); // toInt32(expr) + SQL_FUNCTION_MAP.put("toDateTimeOrZero", ""); // toInt64(expr) 尝试把字符串转为 DateTime + SQL_FUNCTION_MAP.put("toDateTimeOrNull", ""); // toInt(8|16|32|64) 尝试把字符串转为 DateTime 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("toStringCutToZero", ""); // 接受String或FixedString参数,返回String,其内容在找到的第一个零字节处被截断。 SQL_FUNCTION_MAP.put("toDecimal256", ""); SQL_FUNCTION_MAP.put("toDecimal32OrNull", ""); SQL_FUNCTION_MAP.put("toDecimal64OrNull", ""); @@ -636,8 +705,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { SQL_FUNCTION_MAP.put("toDecimal128OrZero", ""); SQL_FUNCTION_MAP.put("toDecimal256OrZero", ""); - - SQL_FUNCTION_MAP.put("toIntervalSecond", ""); //把一个数值类型的值转换为Interval类型的数据。 + SQL_FUNCTION_MAP.put("toIntervalSecond", ""); // 把一个数值类型的值转换为Interval类型的数据。 SQL_FUNCTION_MAP.put("toIntervalMinute", ""); SQL_FUNCTION_MAP.put("toIntervalHour", ""); SQL_FUNCTION_MAP.put("toIntervalDay", ""); @@ -645,93 +713,194 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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("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 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地址格式无效,则返回空字节字符串。 + // ClickHouse ip 地址函数 + SQL_FUNCTION_MAP.put("IPv4NumToString", ""); // 接受一个 UInt32(大端)表示的IPv4的地址,返回相应IPv4的字符串表现形式 + 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("cutIPv6", ""); // 接受一个FixedString(16)类型的IPv6地址,返回 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("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", ""); // least(a, b) 返回a和b中最小的值。 + SQL_FUNCTION_MAP.put("greatest", ""); // greatest(a, b) 返回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) + 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", ""); // roundToExp2(num) 接受一个数字。如果数字小于1,它返回0。 + SQL_FUNCTION_MAP.put("roundDuration", ""); // roundDuration(num) 接受一个数字。如果数字小于1,它返回0。 + SQL_FUNCTION_MAP.put("roundAge", ""); // roundAge(age) 接受一个数字。如果数字小于18,它返回0。 + SQL_FUNCTION_MAP.put("roundDown", ""); // roundDown(num, arr) 接受一个数字并将其舍入到指定数组中的一个元素 + SQL_FUNCTION_MAP.put("bitAnd", ""); // bitAnd(a,b) + SQL_FUNCTION_MAP.put("bitOr", ""); // bitOr(a,b) + + // PostgreSQL 表结构相关 SQL 函数 + SQL_FUNCTION_MAP.put("obj_description", ""); + SQL_FUNCTION_MAP.put("col_description", ""); + + // SQLServer 相关 SQL 函数 + SQL_FUNCTION_MAP.put("len", ""); + SQL_FUNCTION_MAP.put("datalength", ""); + + // Milvus 相关 SQL 函数 + SQL_FUNCTION_MAP.put("vMatch", ""); + SQL_FUNCTION_MAP.put("consistencyLevel", ""); + SQL_FUNCTION_MAP.put("partitionBy", ""); + SQL_FUNCTION_MAP.put("gracefulTime", ""); + SQL_FUNCTION_MAP.put("guaranteeTimestamp", ""); + SQL_FUNCTION_MAP.put("roundDecimal", ""); + SQL_FUNCTION_MAP.put("travelTimestamp", ""); + SQL_FUNCTION_MAP.put("nProbe", ""); + SQL_FUNCTION_MAP.put("ef", ""); + SQL_FUNCTION_MAP.put("searchK", ""); + + } + + private Parser parser; + @Override + public Parser getParser() { + if (parser == null && objectParser != null) { + parser = objectParser.getParser(); + } + return parser; + } + @Override + public AbstractSQLConfig setParser(Parser parser) { + this.parser = parser; + return this; + } + public AbstractSQLConfig putWarnIfNeed(String type, String warn) { + if (Log.DEBUG && parser instanceof AbstractParser) { + ((AbstractParser) parser).putWarnIfNeed(type, warn); + } + return this; + } + public AbstractSQLConfig putWarn(String type, String warn) { + if (Log.DEBUG && parser instanceof AbstractParser) { + ((AbstractParser) parser).putWarn(type, warn); + } + return this; + } + private ObjectParser objectParser; + @Override + public ObjectParser getObjectParser() { + return objectParser; + } + @Override + public AbstractSQLConfig setObjectParser(ObjectParser objectParser) { + this.objectParser = objectParser; + return this; + } + + private int version; + @Override + public int getVersion() { + if (version <= 0 && parser != null) { + version = parser.getVersion(); + } + return version; + } + @Override + public AbstractSQLConfig setVersion(int version) { + this.version = version; + return this; } + private String tag; + @Override + public String getTag() { + if (StringUtil.isEmpty(tag) && parser != null) { + tag = parser.getTag(); + } + return tag; + } + @Override + public AbstractSQLConfig setTag(String tag) { + this.tag = tag; + return this; + } + + // mysql8版本以上,子查询支持with as表达式 + private List withAsExprSqlList = null; + protected List withAsExprPreparedValueList = new ArrayList<>(); + private int[] dbVersionNums = null; + @Override + public int[] getDBVersionNums() { + if (dbVersionNums == null || dbVersionNums.length <= 0) { + dbVersionNums = SQLConfig.super.getDBVersionNums(); + } + return dbVersionNums; + } @Override public boolean limitSQLCount() { - return Log.DEBUG == false || AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable()) == false; + return AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable()) == false; + } + @Override + public boolean allowPartialUpdateFailed() { + return allowPartialUpdateFailed(getTable()); + } + public static boolean allowPartialUpdateFailed(String table) { + return ALLOW_PARTIAL_UPDATE_FAIL_TABLE_MAP.containsKey(table); } @NotNull @@ -761,6 +930,8 @@ public String getUserIdKey() { private String role; //发送请求的用户的角色 private boolean distinct = false; private String database; //表所在的数据库类型 + private String namespace; //表所在的命名空间 + private String catalog; //表所在的目录 private String schema; //表所在的数据库名 private String datasource; //数据源 private String table; //表名 @@ -769,6 +940,7 @@ public String getUserIdKey() { private String havingCombine; //聚合函数的字符串数组,','分隔 private Map having; //聚合函数的字符串数组,','分隔 private String order; //排序方式的字符串数组,','分隔 + private Map keyMap; //字段名映射,支持 name_tag:(name,tag) 多字段 IN,year:left(date,4) 截取日期年份等 private List raw; //需要保留原始 SQL 的字段,','分隔 private List json; //需要转为 JSON 的字段,','分隔 private Subquery from; //子查询临时表 @@ -797,7 +969,7 @@ public String getUserIdKey() { private String procedure; - public SQLConfig setProcedure(String procedure) { + public AbstractSQLConfig setProcedure(String procedure) { this.procedure = procedure; return this; } @@ -827,16 +999,16 @@ public RequestMethod getMethod() { return method; } @Override - public AbstractSQLConfig setMethod(RequestMethod method) { + public AbstractSQLConfig setMethod(RequestMethod method) { this.method = method; return this; } @Override public boolean isPrepared() { - return prepared; + return prepared && ! isMongoDB(); // MongoDB JDBC 还不支持预编译; } @Override - public AbstractSQLConfig setPrepared(boolean prepared) { + public AbstractSQLConfig setPrepared(boolean prepared) { this.prepared = prepared; return this; } @@ -845,7 +1017,7 @@ public boolean isMain() { return main; } @Override - public AbstractSQLConfig setMain(boolean main) { + public AbstractSQLConfig setMain(boolean main) { this.main = main; return this; } @@ -856,7 +1028,7 @@ public Object getId() { return id; } @Override - public AbstractSQLConfig setId(Object id) { + public AbstractSQLConfig setId(Object id) { this.id = id; return this; } @@ -866,7 +1038,7 @@ public Object getIdIn() { return idIn; } @Override - public AbstractSQLConfig setIdIn(Object idIn) { + public AbstractSQLConfig setIdIn(Object idIn) { this.idIn = idIn; return this; } @@ -877,7 +1049,7 @@ public Object getUserId() { return userId; } @Override - public AbstractSQLConfig setUserId(Object userId) { + public AbstractSQLConfig setUserId(Object userId) { this.userId = userId; return this; } @@ -887,7 +1059,7 @@ public Object getUserIdIn() { return userIdIn; } @Override - public AbstractSQLConfig setUserIdIn(Object userIdIn) { + public AbstractSQLConfig setUserIdIn(Object userIdIn) { this.userIdIn = userIdIn; return this; } @@ -898,7 +1070,7 @@ public String getRole() { return role; } @Override - public AbstractSQLConfig setRole(String role) { + public AbstractSQLConfig setRole(String role) { this.role = role; return this; } @@ -908,7 +1080,7 @@ public boolean isDistinct() { return distinct; } @Override - public SQLConfig setDistinct(boolean distinct) { + public AbstractSQLConfig setDistinct(boolean distinct) { this.distinct = distinct; return this; } @@ -918,7 +1090,7 @@ public String getDatabase() { return database; } @Override - public SQLConfig setDatabase(String database) { + public AbstractSQLConfig setDatabase(String database) { this.database = database; return this; } @@ -938,6 +1110,7 @@ public boolean isMySQL() { public static boolean isMySQL(String db) { return DATABASE_MYSQL.equals(db); } + @Override public boolean isPostgreSQL() { return isPostgreSQL(getSQLDatabase()); @@ -945,6 +1118,7 @@ public boolean isPostgreSQL() { public static boolean isPostgreSQL(String db) { return DATABASE_POSTGRESQL.equals(db); } + @Override public boolean isSQLServer() { return isSQLServer(getSQLDatabase()); @@ -952,6 +1126,7 @@ public boolean isSQLServer() { public static boolean isSQLServer(String db) { return DATABASE_SQLSERVER.equals(db); } + @Override public boolean isOracle() { return isOracle(getSQLDatabase()); @@ -959,6 +1134,7 @@ public boolean isOracle() { public static boolean isOracle(String db) { return DATABASE_ORACLE.equals(db); } + @Override public boolean isDb2() { return isDb2(getSQLDatabase()); @@ -966,6 +1142,63 @@ public boolean isDb2() { public static boolean isDb2(String db) { return DATABASE_DB2.equals(db); } + + @Override + public boolean isMariaDB() { + return isMariaDB(getSQLDatabase()); + } + public static boolean isMariaDB(String db) { + return DATABASE_MARIADB.equals(db); + } + + @Override + public boolean isTiDB() { + return isTiDB(getSQLDatabase()); + } + public static boolean isTiDB(String db) { + return DATABASE_TIDB.equals(db); + } + + @Override + public boolean isCockroachDB() { + return isCockroachDB(getSQLDatabase()); + } + public static boolean isCockroachDB(String db) { + return DATABASE_COCKROACHDB.equals(db); + } + + @Override + public boolean isDameng() { + return isDameng(getSQLDatabase()); + } + public static boolean isDameng(String db) { + return DATABASE_DAMENG.equals(db); + } + + @Override + public boolean isKingBase() { + return isKingBase(getSQLDatabase()); + } + public static boolean isKingBase(String db) { + return DATABASE_KINGBASE.equals(db); + } + + @Override + public boolean isElasticsearch() { + return isElasticsearch(getSQLDatabase()); + } + public static boolean isElasticsearch(String db) { + return DATABASE_ELASTICSEARCH.equals(db); + } + + @Override + public boolean isManticore() { + return isManticore(getSQLDatabase()); + } + public static boolean isManticore(String db) { + return DATABASE_MANTICORE.equals(db); + } + @Override public boolean isClickHouse() { return isClickHouse(getSQLDatabase()); @@ -973,6 +1206,7 @@ public boolean isClickHouse() { public static boolean isClickHouse(String db) { return DATABASE_CLICKHOUSE.equals(db); } + @Override public boolean isHive() { return isHive(getSQLDatabase()); @@ -982,22 +1216,203 @@ public static boolean isHive(String db) { } @Override - public String getQuote() { - return isMySQL()||isClickHouse() ? "`" : "\""; + public boolean isPresto() { + return isPresto(getSQLDatabase()); + } + public static boolean isPresto(String db) { + return DATABASE_PRESTO.equals(db); } @Override - public String getSchema() { - return schema; + public boolean isTrino() { + return isTrino(getSQLDatabase()); } - /** - * @param sqlTable - * @return - */ + public static boolean isTrino(String db) { + return DATABASE_TRINO.equals(db); + } + + @Override + public boolean isSnowflake() { + return isSnowflake(getSQLDatabase()); + } + public static boolean isSnowflake(String db) { + return DATABASE_SNOWFLAKE.equals(db); + } + + @Override + public boolean isDatabricks() { + return isDatabricks(getSQLDatabase()); + } + public static boolean isDatabricks(String db) { + return DATABASE_DATABRICKS.equals(db); + } + + @Override + public boolean isCassandra() { + return isCassandra(getSQLDatabase()); + } + public static boolean isCassandra(String db) { + return DATABASE_CASSANDRA.equals(db); + } + + @Override + public boolean isMilvus() { + return isMilvus(getSQLDatabase()); + } + public static boolean isMilvus(String db) { + return DATABASE_MILVUS.equals(db); + } + + @Override + public boolean isInfluxDB() { + return isInfluxDB(getSQLDatabase()); + } + public static boolean isInfluxDB(String db) { + return DATABASE_INFLUXDB.equals(db); + } + + @Override + public boolean isTDengine() { + return isTDengine(getSQLDatabase()); + } + public static boolean isTDengine(String db) { + return DATABASE_TDENGINE.equals(db); + } + + @Override + public boolean isTimescaleDB() { + return isTimescaleDB(getSQLDatabase()); + } + public static boolean isTimescaleDB(String db) { + return DATABASE_TIMESCALEDB.equals(db); + } + + + public boolean isIoTDB() { + return isIoTDB(getDatabase()); + } + public static boolean isIoTDB(String db) { + return DATABASE_IOTDB.equals(db); + } + + + @Override + public boolean isRedis() { + return isRedis(getSQLDatabase()); + } + public static boolean isRedis(String db) { + return DATABASE_REDIS.equals(db); + } + + @Override + public boolean isMongoDB() { + return isMongoDB(getSQLDatabase()); + } + public static boolean isMongoDB(String db) { + return DATABASE_MONGODB.equals(db); + } + + @Override + public boolean isKafka() { + return isKafka(getSQLDatabase()); + } + public static boolean isKafka(String db) { + return DATABASE_KAFKA.equals(db); + } + + @Override + public boolean isMQ() { + return isMQ(getSQLDatabase()); + } + public static boolean isMQ(String db) { + return DATABASE_MQ.equals(db) || isKafka(db); + } + + @Override + public boolean isSQLite() { + return isSQLite(getSQLDatabase()); + } + public static boolean isSQLite(String db) { + return DATABASE_SQLITE.equals(db); + } + + @Override + public boolean isDuckDB() { + return isDuckDB(getSQLDatabase()); + } + public static boolean isDuckDB(String db) { + return DATABASE_DUCKDB.equals(db); + } + + @Override + public boolean isSurrealDB() { + return isSurrealDB(getSQLDatabase()); + } + public static boolean isSurrealDB(String db) { + return DATABASE_SURREALDB.equals(db); + } + + @Override + public boolean isOpenGauss() { + return isOpenGauss(getSQLDatabase()); + } + public static boolean isOpenGauss(String db) { + return DATABASE_OPENGAUSS.equals(db); + } + + @Override + public String getQuote() { // MongoDB 同时支持 `tbl` 反引号 和 "col" 双引号 + if(isElasticsearch() || isManticore() || isIoTDB() || isSurrealDB()) { + return ""; + } + return isMySQL() || isMariaDB() || isTiDB() || isClickHouse() || isTDengine() || isMilvus() ? "`" : "\""; + } + + public String quote(String s) { + String q = getQuote(); + return q + s + q; + } + + @Override + public String getSQLNamespace() { + String sch = getNamespace(); // 前端传参 @namespace 优先 + return sch == null ? DEFAULT_NAMESPACE : sch; // 最后代码默认兜底配置 + } + + @Override + public String getNamespace() { + return namespace; + } + + @Override + public AbstractSQLConfig setNamespace(String namespace) { + this.namespace = namespace; + return this; + } + + + @Override + public String getSQLCatalog() { + String catalog = getCatalog(); // 前端传参 @catalog 优先 + return catalog == null ? DEFAULT_CATALOG : catalog; // 最后代码默认兜底配置 + } + + @Override + public String getCatalog() { + return catalog; + } + + @Override + public AbstractSQLConfig setCatalog(String catalog) { + this.catalog = catalog; + return this; + } + @NotNull + @Override public String getSQLSchema() { String table = getTable(); - //强制,避免因为全局默认的 @schema 自动填充进来,导致这几个类的 schema 为 sys 等其它值 + // FIXME 全部默认填充判断是 系统表 则不填充 // 强制,避免因为全局默认的 @schema 自动填充进来,导致这几个类的 schema 为 sys 等其它值 if (Table.TAG.equals(table) || Column.TAG.equals(table)) { return SCHEMA_INFORMATION; //MySQL, PostgreSQL, SQL Server 都有的 } @@ -1007,18 +1422,27 @@ public String getSQLSchema() { if (SysTable.TAG.equals(table) || SysColumn.TAG.equals(table) || ExtendedProperty.TAG.equals(table)) { return SCHEMA_SYS; //SQL Server 在 sys 中的属性比 information_schema 中的要全,能拿到注释 } + if (AllTable.TAG.equals(table) || AllColumn.TAG.equals(table) + || AllTableComment.TAG.equals(table) || AllColumnComment.TAG.equals(table)) { + return ""; //Oracle, Dameng 的 all_tables, dba_tables 和 all_tab_columns, dba_columns 表好像不属于任何 Schema + } - String sch = getSchema(); - return sch == null ? DEFAULT_SCHEMA : sch; + String sch = getSchema(); // 前端传参 @schema 优先 + if (sch == null) { + sch = TABLE_SCHEMA_MAP.get(table); // 其次 Access 表 alias 和 schema 配置 + } + return sch == null ? DEFAULT_SCHEMA : sch; // 最后代码默认兜底配置 } + @Override - public AbstractSQLConfig setSchema(String schema) { + public String getSchema() { + return schema; + } + + @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个单词!"); - } + AbstractFunctionParser.verifySchema(schema, getTable()); } this.schema = schema; return this; @@ -1029,7 +1453,7 @@ public String getDatasource() { return datasource; } @Override - public SQLConfig setDatasource(String datasource) { + public AbstractSQLConfig setDatasource(String datasource) { this.datasource = datasource; return this; } @@ -1049,43 +1473,54 @@ public String getTable() { @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; + // 如果要强制小写,则可在子类重写这个方法再 toLowerCase + // return DATABASE_POSTGRESQL.equals(getDatabase()) ? t.toLowerCase() : t; + String ot = getTable(); + String nt = TABLE_KEY_MAP.get(ot); + return StringUtil.isEmpty(nt) ? ot : nt; } + + @JSONField(serialize = false) @Override public String getTablePath() { String q = getQuote(); + String ns = isSurrealDB() ? getSQLNamespace() : null; + String cl = isPostgreSQL() || isCockroachDB() || isOpenGauss() || isDuckDB() ? getSQLCatalog() : null; String sch = getSQLSchema(); String sqlTable = getSQLTable(); - return (StringUtil.isEmpty(sch, true) ? "" : q + sch + q + ".") + q + sqlTable + q + ( isKeyPrefix() ? " AS " + getAliasWithQuote() : ""); + return (StringUtil.isEmpty(ns, true) ? "" : q + ns + q + ".") + + (StringUtil.isEmpty(cl, true) ? "" : q + cl + q + ".") + + (StringUtil.isEmpty(sch, true) ? "" : q + sch + q + ".") + + q + sqlTable + q + (isKeyPrefix() ? getAs() + q + getSQLAlias() + q : ""); } @Override - public AbstractSQLConfig setTable(String table) { //Table已经在Parser中校验,所以这里不用防SQL注入 + public AbstractSQLConfig setTable(String table) { //Table已经在Parser中校验,所以这里不用防SQL注入 this.table = table; return this; } + public String getAs() { + return isOracle() || isManticore() ? " " : " AS "; + } + @Override public String getAlias() { return alias; } @Override - public AbstractSQLConfig setAlias(String alias) { + public AbstractSQLConfig setAlias(String alias) { this.alias = alias; return this; } - public String getAliasWithQuote() { - String a = getAlias(); - if (StringUtil.isEmpty(a, true)) { - a = getTable(); - } + public String getSQLAliasWithQuote() { + String a = getSQLAlias(); String q = getQuote(); - //getTable 不能小写,因为Verifier用大小写敏感的名称判断权限 - //如果要强制小写,则可在子类重写这个方法再 toLowerCase return q + (DATABASE_POSTGRESQL.equals(getDatabase()) ? a.toLowerCase() : a) + q; + // getTable 不能小写,因为Verifier用大小写敏感的名称判断权限 + // 如果要强制小写,则可在子类重写这个方法再 toLowerCase + // return q + (DATABASE_POSTGRESQL.equals(getDatabase()) ? a.toLowerCase() : a) + q; return q + a + q; } @@ -1093,11 +1528,11 @@ public String getAliasWithQuote() { public String getGroup() { return group; } - public AbstractSQLConfig setGroup(String... keys) { + public AbstractSQLConfig setGroup(String... keys) { return setGroup(StringUtil.getString(keys)); } @Override - public AbstractSQLConfig setGroup(String group) { + public AbstractSQLConfig setGroup(String group) { this.group = group; return this; } @@ -1117,9 +1552,9 @@ public String getGroupString(boolean hasPrefix) { if (cfg != null) { cfg.setMain(false).setKeyPrefix(true); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } + //if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + //} String c = ((AbstractSQLConfig) cfg).getGroupString(false); if (StringUtil.isEmpty(c, true) == false) { @@ -1138,7 +1573,8 @@ public String getGroupString(boolean hasPrefix) { } for (int i = 0; i < keys.length; i++) { - if (isPrepared()) { //不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! + if (isPrepared()) { + // 不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! if (StringUtil.isName(keys[i]) == false) { throw new IllegalArgumentException("@group:value 中 value里面用 , 分割的每一项都必须是1个单词!并且不要有空格!"); } @@ -1155,7 +1591,7 @@ public String getHavingCombine() { return havingCombine; } @Override - public SQLConfig setHavingCombine(String havingCombine) { + public AbstractSQLConfig setHavingCombine(String havingCombine) { this.havingCombine = havingCombine; return this; } @@ -1165,17 +1601,17 @@ public Map getHaving() { return having; } @Override - public SQLConfig setHaving(Map having) { + public AbstractSQLConfig setHaving(Map having) { this.having = having; return this; } - public AbstractSQLConfig setHaving(String... conditions) { + 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 + * @throws Exception */ @JSONField(serialize = false) public String getHavingString(boolean hasPrefix) throws Exception { @@ -1193,9 +1629,9 @@ public String getHavingString(boolean hasPrefix) throws Exception { if (cfg != null) { cfg.setMain(false).setKeyPrefix(true); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } + //if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + //} String c = ((AbstractSQLConfig) cfg).getHavingString(false); if (StringUtil.isEmpty(c, true) == false) { @@ -1213,7 +1649,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)); + // 提前把 @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,避免额外拷贝 @@ -1223,12 +1659,14 @@ public String getHavingString(boolean hasPrefix) throws Exception { // } //fun0(arg0,arg1,...);fun1(arg0,arg1,...) - String havingString = parseCombineExpression(getMethod(), getQuote(), getTable(), getAliasWithQuote(), map, getHavingCombine(), true, containRaw, true); + String havingString = parseCombineExpression(getMethod(), getQuote(), getTable() + , getAlias(), map, getHavingCombine(), true, containRaw, true); return (hasPrefix ? " HAVING " : "") + StringUtil.concat(havingString, joinHaving, AND); } - protected String getHavingItem(String quote, String table, String alias, String key, String expression, boolean containRaw) throws Exception { + protected String getHavingItem(String quote, String table, String alias + , String key, String expression, boolean containRaw) throws Exception { //fun(arg0,arg1,...) if (containRaw) { String rawSQL = getRawSQL(KEY_HAVING, expression); @@ -1274,47 +1712,6 @@ 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; - // } - // } - // - // return method + "(" + StringUtil.getString(ckeys) + ")" + suffix; - return method + parseSQLExpression(KEY_HAVING, expression.substring(start), containRaw, false, null); } @@ -1322,11 +1719,11 @@ else if (SQL_FUNCTION_MAP.containsKey(method) == false) { public String getOrder() { return order; } - public AbstractSQLConfig setOrder(String... conditions) { + public AbstractSQLConfig setOrder(String... conditions) { return setOrder(StringUtil.getString(conditions)); } @Override - public AbstractSQLConfig setOrder(String order) { + public AbstractSQLConfig setOrder(String order) { this.order = order; return this; } @@ -1346,9 +1743,9 @@ public String getOrderString(boolean hasPrefix) { if (cfg != null) { cfg.setMain(false).setKeyPrefix(true); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } + //if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + //} String c = ((AbstractSQLConfig) cfg).getOrderString(false); if (StringUtil.isEmpty(c, true) == false) { @@ -1366,13 +1763,15 @@ public String getOrderString(boolean hasPrefix) { // return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(order, joinOrder, ", "); // } - if (getCount() > 0 && (isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY.去掉Oracle,Oracle里面没有offset关键字 + 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 String idKey = getIdKey(); if (StringUtil.isEmpty(idKey, true)) { - idKey = "id"; //ORDER BY NULL 不行,SQL Server 会报错,必须要有排序,才能使用 OFFSET FETCH,如果没有 idKey,请求中指定 @order 即可 + idKey = "id"; + // ORDER BY NULL 不行,SQL Server 会报错,必须要有排序,才能使用 OFFSET FETCH,如果没有 idKey,请求中指定 @order 即可 } order = idKey; //让数据库调控默认升序还是降序 + "+"; } @@ -1434,12 +1833,22 @@ public String getOrderString(boolean hasPrefix) { return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinOrder, ", "); } + @Override + public Map getKeyMap() { + return keyMap; + } + @Override + public AbstractSQLConfig setKeyMap(Map keyMap) { + this.keyMap = keyMap; + return this; + } + @Override public List getRaw() { return raw; } @Override - public SQLConfig setRaw(List raw) { + public AbstractSQLConfig setRaw(List raw) { this.raw = raw; return this; } @@ -1452,7 +1861,7 @@ public SQLConfig setRaw(List raw) { */ @Override public String getRawSQL(String key, Object value) throws Exception { - return getRawSQL(key, value, false); + return getRawSQL(key, value, ! ALLOW_MISSING_KEY_4_COMBINE); } /**获取原始 SQL 片段 * @param key @@ -1481,6 +1890,9 @@ public String getRawSQL(String key, Object value, boolean throwWhenMissing) thro throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + "对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); } + + putWarnIfNeed(JSONRequest.KEY_RAW, "@raw:value 的 value 中 " + + key + " 不合法!对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); } else if (rawSQL.isEmpty()) { return (String) value; @@ -1496,7 +1908,7 @@ public List getJson() { return json; } @Override - public AbstractSQLConfig setJson(List json) { + public AbstractSQLConfig setJson(List json) { this.json = json; return this; } @@ -1507,7 +1919,7 @@ public Subquery getFrom() { return from; } @Override - public AbstractSQLConfig setFrom(Subquery from) { + public AbstractSQLConfig setFrom(Subquery from) { this.from = from; return this; } @@ -1517,7 +1929,7 @@ public List getColumn() { return column; } @Override - public AbstractSQLConfig setColumn(List column) { + public AbstractSQLConfig setColumn(List column) { this.column = column; return this; } @@ -1528,6 +1940,8 @@ public String getColumnString() throws Exception { @JSONField(serialize = false) public String getColumnString(boolean inSQLJoin) throws Exception { List column = getColumn(); + String as = getAs(); + String q = getQuote(); switch (getMethod()) { case HEAD: @@ -1551,19 +1965,23 @@ public String getColumnString(boolean inSQLJoin) throws Exception { String alias = index < 0 ? null : c.substring(index + 1); if (alias != null && StringUtil.isName(alias) == false) { - throw new IllegalArgumentException("HEAD请求: 字符 " + alias + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + 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 || 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里面用 , 分割的每一项" + throw new IllegalArgumentException("HEAD请求: 字符 " + origin.substring(0, start) + + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); } } @@ -1584,25 +2002,24 @@ public String getColumnString(boolean inSQLJoin) throws Exception { 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` + // 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); + if (args != null && args.length > 0) { + List raw = getRaw(); + boolean containRaw = raw != null && raw.contains(KEY_COLUMN); + c0 = parseSQLExpression(KEY_COLUMN, c0, containRaw, false, null); } - List raw = getRaw(); - boolean containRaw = raw != null && raw.contains(KEY_COLUMN); - - return SQL.count(parseSQLExpression(KEY_COLUMN, c0, containRaw, false, null)); + return "count(" + c0 + ")" + as + q + JSONResponse.KEY_COUNT + q; } } - return SQL.count(onlyOne ? getKey(c0) : "*"); + return "count(" + (onlyOne ? getKey(c0) : "*") + ")" + as + q + JSONResponse.KEY_COUNT + q; // return SQL.count(onlyOne && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); case POST: if (column == null || column.isEmpty()) { @@ -1612,7 +2029,8 @@ public String getColumnString(boolean inSQLJoin) throws Exception { String s = ""; boolean pfirst = true; for (String c : column) { - if (isPrepared() && StringUtil.isName(c) == false) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + 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)); @@ -1626,28 +2044,28 @@ public String getColumnString(boolean inSQLJoin) throws Exception { String joinColumn = ""; if (joinList != null) { boolean first = true; - for (Join j : joinList) { - if (j.isAppJoin()) { + for (Join join : joinList) { + if (join.isAppJoin()) { continue; } - SQLConfig ocfg = j.getOuterConfig(); + SQLConfig ocfg = join.getOuterConfig(); boolean isEmpty = ocfg == null || ocfg.getColumn() == null; - boolean isLeftOrRightJoin = j.isLeftOrRightJoin(); + boolean isLeftOrRightJoin = join.isLeftOrRightJoin(); if (isEmpty && isLeftOrRightJoin) { - // 改为 SELECT ViceTable.* 解决 SELECT sum(ViceTable.id) LEFT/RIGHT JOIN (SELECT sum(id) FROM ViceTable...) AS ViceTable + // 改为 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 + ".*"; + joinColumn += (first ? "" : ", ") + q + SQLConfig.getSQLAlias(join.getTable(), join.getAlias()) + q + ".*"; first = false; } else { - SQLConfig cfg = isLeftOrRightJoin == false && isEmpty ? j.getJoinConfig() : ocfg; + SQLConfig cfg = isLeftOrRightJoin == false && isEmpty ? join.getJoinConfig() : ocfg; if (cfg != null) { cfg.setMain(false).setKeyPrefix(true); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } + //if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + //} String c = ((AbstractSQLConfig) cfg).getColumnString(true); if (StringUtil.isEmpty(c, true) == false) { @@ -1661,9 +2079,8 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } } - String tableAlias = getAliasWithQuote(); - - // String c = StringUtil.getString(column); //id,name;json_length(contactIdList):contactCount;... + String tableAlias = q + getSQLAlias() + q; + // 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) { @@ -1674,7 +2091,6 @@ public String getColumnString(boolean inSQLJoin) throws Exception { return StringUtil.concat(mc, joinColumn, ", ", true); } - List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_COLUMN); @@ -1688,26 +2104,27 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } // 简单点, 后台配置就带上 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; - // } + 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 提前处理好的 + keys[i] = pre + (hasAlias ? getAs() + q + alias + q : ""); + continue; + } } if (expression.length() > 100) { throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); } - keys[i] = parseSQLExpression(KEY_COLUMN, expression, containRaw, true, "@column:\"column0,column1:alias1;function0(arg0,arg1,...);function1(...):alias2...\""); + keys[i] = parseSQLExpression(KEY_COLUMN, expression, containRaw, true + , "@column:\"column0,column1:alias1;function0(arg0,arg1,...);function1(...):alias2...\""); } String c = StringUtil.getString(keys); c = c + (StringUtil.isEmpty(joinColumn, true) ? "" : ", " + joinColumn);//不能在这里改,后续还要用到: - return isMain() && isDistinct() ? PREFFIX_DISTINCT + c : c; + return isMain() && isDistinct() ? PREFIX_DISTINCT + c : c; default: throw new UnsupportedOperationException( "服务器内部错误:getColumnString 不支持 " + RequestMethod.getName(getMethod()) @@ -1721,7 +2138,6 @@ public String getColumnString(boolean inSQLJoin) throws Exception { * @param expression * @param containRaw * @param allowAlias - * @param columnPrefix * @return */ public String parseSQLExpression(String key, String expression, boolean containRaw, boolean allowAlias) { @@ -1732,11 +2148,20 @@ public String parseSQLExpression(String key, String expression, boolean containR * @param expression * @param containRaw * @param allowAlias - * @param columnPrefix * @param example * @return */ public String parseSQLExpression(String key, String expression, boolean containRaw, boolean allowAlias, String example) { + if (containRaw) { + String s = RAW_MAP.get(expression); + if ("".equals(s)) { + return expression; + } + if (s != null) { + return s; + } + } + String quote = getQuote(); int start = expression.indexOf('('); if (start < 0) { @@ -1790,9 +2215,9 @@ public String parseSQLExpression(String key, String expression, boolean containR } String s = expression.substring(start + 1, end); - boolean distinct = s.startsWith(PREFFIX_DISTINCT); + boolean distinct = s.startsWith(PREFIX_DISTINCT); if (distinct) { - s = s.substring(PREFFIX_DISTINCT.length()); + s = s.substring(PREFIX_DISTINCT.length()); } // 解析函数内的参数 @@ -1818,8 +2243,8 @@ public String parseSQLExpression(String key, String expression, boolean containR + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); } - String origin = fun + "(" + (distinct ? PREFFIX_DISTINCT : "") + StringUtil.getString(ckeys) + ")" + suffix; - expression = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); + String origin = fun + "(" + (distinct ? PREFIX_DISTINCT : "") + StringUtil.getString(ckeys) + ")" + suffix; + expression = origin + (StringUtil.isEmpty(alias, true) ? "" : getAs() + quote + alias + quote); } else { //是窗口函数 fun(arg0,agr1) OVER (agr0 agr1 ...) @@ -1844,13 +2269,15 @@ public String parseSQLExpression(String key, String expression, boolean containR } } else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { - throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); } } // 获取前半部分函数的参数解析 fun(arg0,agr1) - String agrsString1[] = parseArgsSplitWithComma(s1.substring(index1 + 1, s1.lastIndexOf(")")), false, containRaw, allowAlias); + String agrsString1[] = parseArgsSplitWithComma( + s1.substring(index1 + 1, s1.lastIndexOf(")")), false, containRaw, allowAlias + ); int index2 = s2.indexOf("("); // 后半部分 “(”的起始位置 String argString2 = s2.substring(index2 + 1, end); // 后半部分的参数 @@ -1862,7 +2289,7 @@ else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { + key + ":value 中 value里面用 ; 分割的每一项" + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); } - + String suffix = s2.substring(end + 1, aliasIndex < 0 ? s2.length() : aliasIndex); if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { @@ -1870,28 +2297,31 @@ else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { + ":\"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) + ")" + suffix + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } + String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw, allowAlias); + expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") + + StringUtil.getString(argsString2) + ")" + suffix // 传参不传空格,拼接带空格 + + (StringUtil.isEmpty(alias, true) ? "" : getAs() + quote + alias + quote); + } } return expression; } - + /**解析函数参数或者字段,此函数对于解析字段 和 函数内参数通用 * @param param * @param isColumn true:不是函数参数。false:是函数参数 - * @param containRaw - * @param allowAlias + * @param containRaw + * @param allowAlias * @return */ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean containRaw, boolean allowAlias) { // 以"," 分割参数 String quote = getQuote(); - String tableAlias = getAliasWithQuote(); + boolean isKeyPrefix = isKeyPrefix(); + String tableAlias = quote + getSQLAlias() + quote; String ckeys[] = StringUtil.split(param); // 以","分割参数 if (ckeys != null && ckeys.length > 0) { @@ -1900,7 +2330,7 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean String origin; String alias; - + // 如果参数包含 "'" ,解析字符串 if (ck.startsWith("`") && ck.endsWith("`")) { origin = ck.substring(1, ck.length() - 1); @@ -1949,10 +2379,13 @@ else if ("!=null".equals(ck)) { + "关键字必须全大写,且以空格分隔的参数,空格必须只有 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 个空格!其它情况不允许空格!"); + + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + + PATTERN_FUNCTION + " 且不包含连续减号 -- !" + + "DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); } } } @@ -1961,12 +2394,10 @@ else if ("!=null".equals(ck)) { // 以空格分割参数 String[] mkes = containRaw ? StringUtil.split(ck, " ", true) : new String[]{ ck }; - //如果参数中含有空格(少数情况) 比如 fun(arg1 arg2 arg3 ,arg4) 中的 arg1 arg2 arg3,比如 DISTINCT id + //如果参数中含有空格(少数情况) 比如 fun(arg1, arg2,arg3,arg4) 中的 arg1 arg2 arg3,比如 DISTINCT id if (mkes != null && mkes.length >= 2) { - origin = praseArgsSplitWithSpace(mkes); + origin = parseArgsSplitWithSpace(mkes); } else { - boolean isName = false; - String mk = RAW_MAP.get(origin); if (mk != null) { // newSQLConfig 提前处理好的 if (mk.length() > 0) { @@ -1974,19 +2405,34 @@ else if ("!=null".equals(ck)) { } } else if (StringUtil.isNumer(origin)) { //do nothing - } else if (StringUtil.isName(origin)) { - origin = quote + origin + quote; - isName = true; } else { - origin = getValue(origin).toString(); - } + String[] keys = origin.split("[.]"); + StringBuilder sb = new StringBuilder(); + + int len = keys == null ? 0 : keys.length; + if (len > 0) { + boolean first = true; + for (String k : keys) { + if (StringUtil.isName(k) == false) { + sb = null; + break; + } + + sb.append(first ? "" : ".").append(quote).append(k).append(quote); + first = false; + } + } - if (isName && isKeyPrefix()) { - origin = tableAlias + "." + origin; + String s = sb == null ? null : sb.toString(); + if (StringUtil.isNotEmpty(s, true)) { + origin = (len == 1 && isKeyPrefix ? tableAlias + "." : "") + s; + } else { + origin = getValue(origin).toString(); + } } if (isColumn && StringUtil.isEmpty(alias, true) == false) { - origin += " AS " + quote + alias + quote; + origin += getAs() + quote + alias + quote; } } } @@ -2006,9 +2452,10 @@ else if ("!=null".equals(ck)) { * @param mkes * @return */ - private String praseArgsSplitWithSpace(String mkes[]) { + private String parseArgsSplitWithSpace(String mkes[]) { String quote = getQuote(); - String tableAlias = getAliasWithQuote(); + boolean isKeyPrefix = isKeyPrefix(); + String tableAlias = quote + getSQLAlias() + quote; // 包含空格的参数 肯定不包含别名 不用处理别名 if (mkes != null && mkes.length > 0) { @@ -2035,7 +2482,7 @@ private String praseArgsSplitWithSpace(String mkes[]) { + " 中所有字符串 column 都必须必须为1个单词 !"); } - mkes[j] = getKey(origin).toString(); + mkes[j] = getKey(origin); continue; } else if (ck.startsWith("'") && ck.endsWith("'")) { @@ -2050,24 +2497,40 @@ else if (ck.startsWith("'") && ck.endsWith("'")) { mkes[j] = getValue(origin).toString(); continue; } - else if (ck.contains("`") || ck.contains("'") || origin.startsWith("_") || origin.contains("--")) { // || PATTERN_FUNCTION.matcher(origin).matches() == false) { + 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 个空格!其它情况不允许空格!"); + + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); } - 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(); - } + String[] keys = origin.split("[.]"); + StringBuilder sb = new StringBuilder(); + + int len = keys == null ? 0 : keys.length; + if (len > 0) { + boolean first = true; + for (String k : keys) { + if (StringUtil.isName(k) == false) { + sb = null; + break; + } - if (isName && isKeyPrefix()) { - origin = tableAlias + "." + origin; + sb.append(first ? "" : ".").append(quote).append(k).append(quote); + first = false; + } + } + + String s = sb == null ? null : sb.toString(); + if (StringUtil.isNotEmpty(s, true)) { + origin = (len == 1 && isKeyPrefix ? tableAlias + "." : "") + s; + } else { + origin = getValue(origin).toString(); + } } mkes[j] = origin; @@ -2105,7 +2568,7 @@ public String getValuesString() { return s; } @Override - public AbstractSQLConfig setValues(List> valuess) { + public AbstractSQLConfig setValues(List> valuess) { this.values = valuess; return this; } @@ -2115,7 +2578,7 @@ public Map getContent() { return content; } @Override - public AbstractSQLConfig setContent(Map content) { + public AbstractSQLConfig setContent(Map content) { this.content = content; return this; } @@ -2125,7 +2588,7 @@ public int getCount() { return count; } @Override - public AbstractSQLConfig setCount(int count) { + public AbstractSQLConfig setCount(int count) { this.count = count; return this; } @@ -2134,7 +2597,7 @@ public int getPage() { return page; } @Override - public AbstractSQLConfig setPage(int page) { + public AbstractSQLConfig setPage(int page) { this.page = page; return this; } @@ -2143,7 +2606,7 @@ public int getPosition() { return position; } @Override - public AbstractSQLConfig setPosition(int position) { + public AbstractSQLConfig setPosition(int position) { this.position = position; return this; } @@ -2153,7 +2616,7 @@ public int getQuery() { return query; } @Override - public AbstractSQLConfig setQuery(int query) { + public AbstractSQLConfig setQuery(int query) { this.query = query; return this; } @@ -2162,7 +2625,7 @@ public Boolean getCompat() { return compat; } @Override - public AbstractSQLConfig setCompat(Boolean compat) { + public AbstractSQLConfig setCompat(Boolean compat) { this.compat = compat; return this; } @@ -2172,7 +2635,7 @@ public int getType() { return type; } @Override - public AbstractSQLConfig setType(int type) { + public AbstractSQLConfig setType(int type) { this.type = type; return this; } @@ -2182,12 +2645,12 @@ public int getCache() { return cache; } @Override - public AbstractSQLConfig setCache(int cache) { + public AbstractSQLConfig setCache(int cache) { this.cache = cache; return this; } - public AbstractSQLConfig setCache(String cache) { + public AbstractSQLConfig setCache(String cache) { return setCache(getCache(cache)); } public static int getCache(String cache) { @@ -2214,7 +2677,8 @@ public static int getCache(String cache) { cache2 = JSONRequest.CACHE_RAM; break; default: - throw new IllegalArgumentException(JSONRequest.KEY_CACHE + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); + throw new IllegalArgumentException(JSONRequest.KEY_CACHE + + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); } } return cache2; @@ -2225,7 +2689,7 @@ public boolean isExplain() { return explain; } @Override - public AbstractSQLConfig setExplain(boolean explain) { + public AbstractSQLConfig setExplain(boolean explain) { this.explain = explain; return this; } @@ -2235,7 +2699,7 @@ public List getJoinList() { return joinList; } @Override - public SQLConfig setJoinList(List joinList) { + public AbstractSQLConfig setJoinList(List joinList) { this.joinList = joinList; return this; } @@ -2250,7 +2714,7 @@ public boolean isTest() { return test; } @Override - public AbstractSQLConfig setTest(boolean test) { + public AbstractSQLConfig setTest(boolean test) { this.test = test; return this; } @@ -2275,20 +2739,69 @@ public static int getOffset(int page, int count) { */ @JSONField(serialize = false) public String getLimitString() { - if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { + int count = getCount(); + + if (isMilvus()) { + if (count == 0) { + Parser parser = getParser(); + count = parser == null ? AbstractParser.MAX_QUERY_COUNT : parser.getMaxQueryCount(); + } + + int offset = getOffset(getPage(), count); + return " LIMIT " + offset + ", " + count; // 目前 moql-transx 的限制 + } else if (isSurrealDB()) { + if (count == 0) { + Parser parser = getParser(); + count = parser == null ? AbstractParser.MAX_QUERY_COUNT : parser.getMaxQueryCount(); + } + + int offset = getOffset(getPage(), count); + return " START " + offset + " LIMIT " + count; + } + + if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { // TODO HEAD 真的不需要 LIMIT ? return ""; } - return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2(), isOracle()); - } - /**获取限制数量 - * @param limit - * @return - */ + + return getLimitString( + getPage() + , getCount() + , isOracle() || isSQLServer() || isDb2() + , isOracle() || isDameng() || isKingBase() + , isPresto() || isTrino() + ); + } + /**获取限制数量及偏移量 + * @param page + * @param count + * @param isTSQL + * @param isOracle + * @return + */ public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle) { + return getLimitString(page, count, isTSQL, isOracle, false); + } + /**获取限制数量及偏移量 + * @param page + * @param count + * @param isTSQL + * @param isOracle + * @param isPresto + * @return + */ + public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle, boolean isPresto) { int offset = getOffset(page, count); + if (isOracle) { // TODO 判断版本,高版本可以用 OFFSET FETCH + return " WHERE ROWNUM BETWEEN " + offset + " AND " + (offset + 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 " OFFSET " + offset + " ROWS FETCH FIRST " + count + " ROWS ONLY"; + } + + if (isPresto) { // https://prestodb.io/docs/current/sql/select.html + return (offset <= 0 ? "" : " OFFSET " + offset) + " LIMIT " + count; } return " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET @@ -2299,7 +2812,7 @@ public List getNull() { return nulls; } @Override - public SQLConfig setNull(List nulls) { + public AbstractSQLConfig setNull(List nulls) { this.nulls = nulls; return this; } @@ -2309,7 +2822,7 @@ public Map getCast() { return cast; } @Override - public SQLConfig setCast(Map cast) { + public AbstractSQLConfig setCast(Map cast) { this.cast = cast; return this; } @@ -2341,7 +2854,7 @@ public String getCombine() { return combine; } @Override - public AbstractSQLConfig setCombine(String combine) { + public AbstractSQLConfig setCombine(String combine) { this.combine = combine; return this; } @@ -2360,7 +2873,7 @@ public Map> getCombineMap() { return combineMap; } @Override - public AbstractSQLConfig setCombineMap(Map> combineMap) { + public AbstractSQLConfig setCombineMap(Map> combineMap) { this.combineMap = combineMap; return this; } @@ -2370,7 +2883,7 @@ public Map getWhere() { return where; } @Override - public AbstractSQLConfig setWhere(Map where) { + public AbstractSQLConfig setWhere(Map where) { this.where = where; return this; } @@ -2415,10 +2928,10 @@ public Object getWhere(String key, boolean exactMatch) { return null; } @Override - public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { + public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { if (key != null) { if (where == null) { - where = new LinkedHashMap(); + where = new LinkedHashMap(); } if (value == null) { where.remove(key); @@ -2488,9 +3001,10 @@ else if (key.equals(userIdInKey)) { } if (prior) { - andList.add(i, key); //userId的优先级不能比id高 0, key); + andList.add(i, key); // userId 的优先级不能比 id 高 0, key); } else { - andList.add(key); //AbstractSQLExecutor.onPutColumn里getSQL,要保证缓存的SQL和查询的SQL里 where 的 key:value 顺序一致 + // AbstractSQLExecutor.onPutColumn 里 getSQL,要保证缓存的 SQL 和查询的 SQL 里 where 的 key:value 顺序一致 + andList.add(key); } } combineMap.put("&", andList); @@ -2501,7 +3015,7 @@ else if (key.equals(userIdInKey)) { /**获取WHERE * @return - * @throws Exception + * @throws Exception */ @JSONField(serialize = false) @Override @@ -2516,12 +3030,13 @@ public String getWhereString(boolean hasPrefix) throws Exception { * @param method * @param where * @return - * @throws Exception + * @throws Exception */ @JSONField(serialize = false) - public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, String combine, List joinList, boolean verifyName) throws Exception { - - String whereString = parseCombineExpression(method, getQuote(), getTable(), getAliasWithQuote(), where, combine, verifyName, false, false); + public String getWhereString(boolean hasPrefix, RequestMethod method, Map where + , String combine, List joinList, boolean verifyName) throws Exception { + String whereString = parseCombineExpression(method, getQuote(), getTable(), getAlias() + , where, combine, verifyName, false, false); whereString = concatJoinWhereString(whereString); String result = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; @@ -2537,7 +3052,7 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map conditioinMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception { + , Map conditionMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception { String errPrefix = table + (isHaving ? ":{ @having:{ " : ":{ ") + "@combine:'" + combine + (isHaving ? "' } }" : "' }"); String s = StringUtil.getString(combine); @@ -2556,10 +3071,10 @@ protected String parseCombineExpression(RequestMethod method, String quote, Stri + "逻辑连接符 & | 左右必须各一个相邻空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); } - if (conditioinMap == null) { - conditioinMap = new HashMap<>(); + if (conditionMap == null) { + conditionMap = new HashMap<>(); } - int size = conditioinMap.size(); + int size = conditionMap.size(); int maxCount = isHaving ? getMaxHavingCount() : getMaxWhereCount(); if (maxCount > 0 && size > maxCount) { @@ -2569,13 +3084,16 @@ protected String parseCombineExpression(RequestMethod method, String quote, Stri String result = ""; - List prepreadValues = getPreparedValueList(); + List preparedValues = getPreparedValueList(); + if (preparedValues == null && isHaving == false) { + preparedValues = new ArrayList<>(); + } Map usedKeyCountMap = new HashMap<>(size); int n = s.length(); if (n > 0) { - if (isHaving == false) { + if (isHaving == false) { // 只收集表达式条件值 setPreparedValueList(new ArrayList<>()); // 必须反过来,否则 JOIN ON 内部 @combine 拼接后顺序错误 } @@ -2610,7 +3128,8 @@ protected String parseCombineExpression(RequestMethod method, String quote, Stri if (isEmpty == false) { if (first == false && lastLogic <= 0) { throw new IllegalArgumentException(errPrefix + " 中字符 " - + "'" + s.substring(i - key.length() - (isOver ? 1 : 0)) + "' 不合法!左边缺少 & | 其中一个逻辑连接符!"); + + "'" + s.substring(i - key.length() - (isOver ? 1 : 0)) + + "' 不合法!左边缺少 & | 其中一个逻辑连接符!"); } allCount ++; @@ -2618,21 +3137,31 @@ protected String parseCombineExpression(RequestMethod method, String quote, Stri throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!" + "其中 key 数量 " + allCount + " 已超过最大值,必须在条件键值对数量 0-" + maxCombineCount + " 内!"); } + + String column = key; + int keyIndex = column.indexOf(":"); + column = keyIndex > 0 ? column.substring(0, keyIndex) : column; + Object value = conditionMap.get(column); + String wi = ""; + if (value == null && conditionMap.containsKey(column) == false) { // 兼容@null + isNot = false; // 以占位表达式为准 + size++; // 兼容 key 数量判断 + wi = keyIndex > 0 ? key.substring(keyIndex + 1) : ""; + if (StringUtil.isEmpty(wi)) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + + key + "' 对应的条件键值对 " + column + ":value 不存在!"); + } + } else { + wi = isHaving ? getHavingItem(quote, table, alias, column, (String) value, containRaw) + : getWhereItem(column, value, method, verifyName); + } + 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 不是有效条件键值对!"); @@ -2642,7 +3171,8 @@ protected String parseCombineExpression(RequestMethod method, String quote, Stri count = count == null ? 1 : count + 1; if (count > maxCombineKeyCount && maxCombineKeyCount > 0) { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!" - + "其中 '" + column + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); + + "其中 '" + column + "' 重复引用,次数 " + count + + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); } usedKeyCountMap.put(column, count); @@ -2672,7 +3202,7 @@ else if (c == '&') { result += SQL.AND; lastLogic = c; i ++; - } + } else { key += c; } @@ -2680,7 +3210,8 @@ else 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)) + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + + "' } 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1)) + "' 不合法!逻辑连接符 | 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + "不允许首尾有空格,也不允许连续空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); } @@ -2700,11 +3231,13 @@ else if (c == '!') { 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 + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + + "' 不合法!非逻辑符 '!' 右边多了一个字符 '" + + next + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); } if (i > 0 && lastLogic <= 0 && last != '(') { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(i) @@ -2734,7 +3267,7 @@ else if (c == '(') { depth ++; if (depth > maxDepth && maxDepth > 0) { - throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!"); } @@ -2751,7 +3284,7 @@ else if (c == ')') { result += c; lastLogic = 0; - } + } else { key += c; } @@ -2766,7 +3299,12 @@ else if (c == ')') { } } - Set> set = conditioinMap.entrySet(); + List exprPreparedValues = getPreparedValueList(); + if (isHaving == false) { // 只收集 AND 条件值 + setPreparedValueList(new ArrayList<>()); + } + + Set> set = conditionMap.entrySet(); String andCond = ""; boolean isItemFirst = true; @@ -2777,7 +3315,8 @@ else if (c == ')') { continue; } - String wi = isHaving ? getHavingItem(quote, table, alias, key, (String) entry.getValue(), containRaw) : getWhereItem(key, entry.getValue(), 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; } @@ -2786,26 +3325,34 @@ else if (c == ')') { isItemFirst = false; } + if (isHaving == false) { // 优先存放 AND 条件值 + preparedValues.addAll(getPreparedValueList()); + } + if (StringUtil.isEmpty(result, true)) { result = andCond; } else if (StringUtil.isNotEmpty(andCond, true)) { // andCond 必须放后面,否则 prepared 值顺序错误 - if (isHaving) { // HAVING 前 WHERE 已经有条件 ? 占位,不能反过来,想优化 AND 连接在最前,需要多遍历一次内部的 key,也可以 newSQLConfig 时存到 andList + if (isHaving) { + // HAVING 前 WHERE 已经有条件 ? 占位,不能反过来,想优化 AND 连接在最前,需要多遍历一次内部的 key,也可以 newSQLConfig 时存到 andList result = "( " + result + " )" + AND + andCond; - } + } else { result = andCond + AND + "( " + result + " )"; // 先暂存之前的 prepared 值,然后反向整合 - if (n > 0) { - prepreadValues.addAll(getPreparedValueList()); - setPreparedValueList(prepreadValues); - } } } + if (isHaving == false) { + if (exprPreparedValues != null && exprPreparedValues.isEmpty() == false) { + preparedValues.addAll(exprPreparedValues); // 在 AND 条件值后存放表达式内的条件值 + } + setPreparedValueList(preparedValues); + } + return result; } - /**已废弃,最快 6.0 删除,请尽快把前端/客户端 @combine:"a,b" 这种旧方式改为 @combine:"a | b" 这种新方式 + /**@combine:"a,b" 条件组合。虽然有了 @combine:"a | b" 这种新方式,但为了 Join 多个 On 能保证顺序正确,以及这个性能更好,还是保留这个方式 * @param hasPrefix * @param method * @param where @@ -2815,8 +3362,8 @@ else if (StringUtil.isNotEmpty(andCond, true)) { // andCond 必须放后面, * @return * @throws Exception */ - @Deprecated - public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { + public String getWhereString(boolean hasPrefix, RequestMethod method, Map where + , Map> combine, List joinList, boolean verifyName) throws Exception { Set>> combineSet = combine == null ? null : combine.entrySet(); if (combineSet == null || combineSet.isEmpty()) { Log.w(TAG, "getWhereString combineSet == null || combineSet.isEmpty() >> return \"\";"); @@ -2897,7 +3444,7 @@ protected String concatJoinWhereString(String whereString) throws Exception { String js; boolean changed = false; - //各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? + // 各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? for (Join j : joinList) { String jt = j.getJoinType(); @@ -2908,9 +3455,9 @@ protected String concatJoinWhereString(String whereString) throws Exception { case ">": // RIGHT JOIN break; - case "&": // INNER JOIN: A & B - case "": // FULL JOIN: A | B - case "|": // FULL JOIN: A | B + case "&": // INNER JOIN: A & B + case "": // FULL JOIN: A | B + case "|": // FULL JOIN: A | B case "!": // OUTER JOIN: ! (A | B) case "^": // SIDE JOIN: ! (A & B) case "(": // ANTI JOIN: A & ! B @@ -2966,7 +3513,7 @@ protected String concatJoinWhereString(String whereString) throws Exception { newWs += AND; } - if (isAntiJoin) { // ( ANTI JOIN: A & ! B + if (isAntiJoin) { // ( ANTI JOIN: A & ! B newWs += " ( " + ( isWsEmpty ? "" : ws + AND ) + NOT + " ( " + js + " ) " + " ) "; } else if (isForeignJoin) { // ) FOREIGN JOIN: (! A) & B // preparedValueList.add 不好反过来 B & ! A @@ -2975,7 +3522,7 @@ else if (isForeignJoin) { // ) FOREIGN JOIN: (! A) & B // preparedValueList.add else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) //MySQL 因为 NULL 值处理问题,(A & ! B) | (B & ! A) 与 ! (A & B) 返回结果不一样,后者往往更多 newWs += " ( " + getCondition( - true, + true, ( isWsEmpty ? "" : ws + AND ) + " ( " + js + " ) " ) + " ) "; } @@ -2983,7 +3530,7 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) int logic = Logic.getType(jt); newWs += " ( " + getCondition( - Logic.isNot(logic), + Logic.isNot(logic), ws + ( isWsEmpty ? "" : (Logic.isAnd(logic) ? AND : OR) ) + " ( " + js + " ) " @@ -3025,21 +3572,24 @@ 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); + // 避免筛选到全部 value = key == null ? null : where.get(key); if (key == null || key.endsWith("()") || key.startsWith("@")) { //关键字||方法, +或-直接报错 Log.d(TAG, "getWhereItem key == null || key.endsWith(()) || key.startsWith(@) >> continue;"); return null; } - if (key.endsWith("@")) {//引用 + if (key.endsWith("@")) { // 引用 // key = key.substring(0, key.lastIndexOf("@")); throw new IllegalArgumentException(TAG + ".getWhereItem: 字符 " + key + " 不合法!"); } + if (value == null) { + return null; + } int keyType; if (key.endsWith("$")) { keyType = 1; - } + } else if (key.endsWith("~")) { keyType = key.charAt(key.length() - 2) == '*' ? -2 : 2; //FIXME StringIndexOutOfBoundsException } @@ -3071,9 +3621,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); + String rawSQL = getRawSQL(key, value); switch (keyType) { case 1: @@ -3109,16 +3659,19 @@ public String getEqualString(String key, String column, Object value, String raw throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !"); } - boolean not = column.endsWith("!"); // & | 没有任何意义,写法多了不好控制 + boolean not = column.endsWith("!"); // & | 没有任何意义,写法多了不好控制 if (not) { column = column.substring(0, column.length() - 1); } - if (StringUtil.isName(column) == false) { + + String rc = column.endsWith("[") || column.endsWith("{") ? column.substring(0, column.length() - 1) : column; + if (StringUtil.isName(rc) == 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))); + return getKey(column) + logic + (value instanceof Subquery ? getSubqueryString((Subquery) value) + : (rawSQL != null ? rawSQL : getValue(key, column, value))); } @JSONField(serialize = false) @@ -3126,26 +3679,54 @@ public String getCompareString(String key, String column, Object value, String t if (value != null && JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { throw new IllegalArgumentException(key + ":value 中 value 不合法!比较运算 [>, <, >=, <=] 只支持 [Boolean, Number, String] 内的类型 !"); } - if (StringUtil.isName(column) == false) { + + String rc = column.endsWith("[") || column.endsWith("{") ? column.substring(0, column.length() - 1) : column; + if ( ! StringUtil.isName(rc)) { throw new IllegalArgumentException(key + ":value 中 key 不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); } - return getKey(column) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(key, column, value))); + return getKey(column) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) + : (rawSQL != null ? rawSQL : getValue(key, column, value))); } public String getKey(String key) { - if (isTest()) { + String lenFun = ""; + if (key.endsWith("[")) { + lenFun = isSQLServer() ? "datalength" : "length"; + key = key.substring(0, key.length() - 1); + } + else if (key.endsWith("{")) { + lenFun = "json_length"; + key = key.substring(0, key.length() - 1); + } + else if (isTest()) { if (key.contains("'")) { // || key.contains("#") || key.contains("--")) { throw new IllegalArgumentException("参数 " + key + " 不合法!key 中不允许有单引号 ' !"); } return getSQLValue(key).toString(); } - return getSQLKey(key); + Map keyMap = getKeyMap(); + String expression = keyMap == null ? null : keyMap.get(key); + if (expression == null) { + expression = COLUMN_KEY_MAP == null ? null : COLUMN_KEY_MAP.get(key); + } + + String sqlKey; + if (expression == null) { + sqlKey = getSQLKey(key); + } + else { + // (name,tag) left(date,4) 等 + List raw = getRaw(); + sqlKey = parseSQLExpression(KEY_KEY, expression, raw != null && raw.contains(KEY_KEY), false); + } + + return lenFun.isEmpty() ? sqlKey : lenFun + "(" + sqlKey + ")"; } public String getSQLKey(String key) { String q = getQuote(); - return (isKeyPrefix() ? getAliasWithQuote() + "." : "") + q + key + q; + return (isKeyPrefix() ? q + getSQLAlias() + q + "." : "") + q + key + q; } /** @@ -3199,8 +3780,10 @@ 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.toString().replaceAll("\\'", "\\\\'") + "'"; //MySQL 隐式转换用不了索引 + // 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 隐式转换用不了索引 } @Override @@ -3208,21 +3791,25 @@ public List getPreparedValueList() { return preparedValueList; } @Override - public AbstractSQLConfig setPreparedValueList(List preparedValueList) { + 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 - */ + * @param key + * @param column + * @param value + * @param rawSQL + * @return {@link #getSearchString(String, String, Object[], int)} + * @throws IllegalArgumentException + */ @JSONField(serialize = false) public String getSearchString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key$ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + throw new UnsupportedOperationException("@raw:value 中 " + + key + " 不合法!@raw 不支持 key$ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } if (value == null) { return ""; @@ -3239,10 +3826,13 @@ public String getSearchString(String key, String column, Object value, String ra return getSearchString(key, column, arr.toArray(), logic.getType()); } /**search key match values - * @param in - * @return LOGIC [ key LIKE 'values[i]' ] - * @throws IllegalArgumentException - */ + * @param key + * @param column + * @param values + * @param type + * @return LOGIC [ key LIKE 'values[i]' ] + * @throws IllegalArgumentException + */ @JSONField(serialize = false) public String getSearchString(String key, String column, Object[] values, int type) throws IllegalArgumentException { if (values == null || values.length <= 0) { @@ -3286,7 +3876,8 @@ public String getLikeString(@NotNull String key, @NotNull String column, String l = k.charAt(k.length() - 1); if (l == '%' || l == '_' || l == '?') { if (l == r) { - throw new IllegalArgumentException(key + ":value 中字符 " + k + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); + throw new IllegalArgumentException(key + ":value 中字符 " + + k + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); } k = k.substring(0, k.length() - 1); @@ -3308,7 +3899,8 @@ else if (l > 0 && StringUtil.isName(String.valueOf(l))) { if (l > 0 || r > 0) { if (value == null) { - throw new IllegalArgumentException(key + ":value 中 value 为 null!key$:value 中 value 不能为 null,且类型必须是 String !"); + throw new IllegalArgumentException(key + ":value 中 value 为 null!" + + "key$:value 中 value 不能为 null,且类型必须是 String !"); } value = value.replaceAll("\\\\", "\\\\\\\\"); @@ -3334,14 +3926,16 @@ else if (l > 0 && StringUtil.isName(String.valueOf(l))) { * @param key * @param column * @param value - * @param ignoreCase - * @return {@link #getRegExpString(String, Object[], int, boolean)} - * @throws IllegalArgumentException + * @param ignoreCase + * @return {@link #getRegExpString(String, String, Object[], int, boolean)} + * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getRegExpString(String key, String column, Object value, boolean ignoreCase, String rawSQL) throws IllegalArgumentException { + 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 !"); + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key~ 这种功能符 !" + + "只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } if (value == null) { return ""; @@ -3360,13 +3954,14 @@ public String getRegExpString(String key, String column, Object value, boolean i /**search key match RegExp values * @param key * @param values - * @param type - * @param ignoreCase + * @param type + * @param ignoreCase * @return LOGIC [ key REGEXP 'values[i]' ] - * @throws IllegalArgumentException + * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getRegExpString(String key, String column, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { + public String getRegExpString(String key, String column, Object[] values, int type, boolean ignoreCase) + throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; } @@ -3376,7 +3971,8 @@ public String getRegExpString(String key, String column, Object[] values, int ty if (values[i] instanceof String == false) { throw new IllegalArgumentException(key + ":value 中value的类型只能为String或String[]!"); } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getRegExpString(key, column, (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); @@ -3390,22 +3986,31 @@ public String getRegExpString(String key, String column, Object[] values, int ty */ @JSONField(serialize = false) public String getRegExpString(String key, String column, String value, boolean ignoreCase) { - if (isPostgreSQL()) { + if (isPostgreSQL() || isCockroachDB() || isInfluxDB()) { return getKey(column) + " ~" + (ignoreCase ? "* " : " ") + getValue(key, column, value); } - if (isOracle()) { + if (isOracle() || isDameng() || isKingBase() || (isMySQL() && getDBVersionNums()[0] >= 8)) { return "regexp_like(" + getKey(column) + ", " + getValue(key, column, value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; } + if (isPresto() || isTrino()) { + return "regexp_like(" + (ignoreCase ? "lower(" : "") + getKey(column) + (ignoreCase ? ")" : "") + + ", " + (ignoreCase ? "lower(" : "") + getValue(key, column, value) + (ignoreCase ? ")" : "") + ")"; + } if (isClickHouse()) { return "match(" + (ignoreCase ? "lower(" : "") + getKey(column) + (ignoreCase ? ")" : "") + ", " + (ignoreCase ? "lower(" : "") + getValue(key, column, value) + (ignoreCase ? ")" : "") + ")"; } + if (isElasticsearch()) { + return getKey(column) + " RLIKE " + getValue(key, column, value); + } if (isHive()) { return (ignoreCase ? "lower(" : "") + getKey(column) + (ignoreCase ? ")" : "") + " REGEXP " + (ignoreCase ? "lower(" : "") + getValue(key, column, value) + (ignoreCase ? ")" : ""); } return getKey(column) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(key, column, value); } + + //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -3416,12 +4021,13 @@ public String getRegExpString(String key, String column, String value, boolean i * @param key * @param value 'start,end' * @return LOGIC [ key BETWEEN 'start' AND 'end' ] - * @throws IllegalArgumentException + * @throws IllegalArgumentException */ @JSONField(serialize = false) public String getBetweenString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key% 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key% 这种功能符 !" + + "只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } if (value == null) { return ""; @@ -3440,9 +4046,11 @@ public String getBetweenString(String key, String column, Object value, String r /**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]] ? + * @param column + * @param values ['start,end'] TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? + * @param type * @return LOGIC [ key BETWEEN 'start' AND 'end' ] - * @throws IllegalArgumentException + * @throws IllegalArgumentException */ @JSONField(serialize = false) public String getBetweenString(String key, String column, Object[] values, int type) throws IllegalArgumentException { @@ -3459,25 +4067,30 @@ public String getBetweenString(String key, String column, Object[] values, int t 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, column, (Object) vs[0], (Object) vs[1]) + ")"; + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + + "(" + getBetweenString(key, column, 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 - */ + * @return key + * @param column + * @param start + * @param end + * @return LOGIC [ key BETWEEN 'start' AND 'end' ] + * @throws IllegalArgumentException + */ @JSONField(serialize = false) public String getBetweenString(String key, String column, Object start, Object end) throws IllegalArgumentException { if (JSON.isBooleanOrNumberOrString(start) == false || JSON.isBooleanOrNumberOrString(end) == false) { - throw new IllegalArgumentException(key + ":value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); + throw new IllegalArgumentException(key + ":value 中 value 不合法!类型为 String 时必须包括1个逗号 , " + + "且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); } return getKey(column) + " BETWEEN " + getValue(key, column, start) + AND + getValue(key, column, end); } @@ -3494,7 +4107,7 @@ public String getBetweenString(String key, String column, Object start, Object e * @param key * @param range "condition0,condition1..." * @return key condition0 AND key condition1 AND ... - * @throws Exception + * @throws Exception */ @JSONField(serialize = false) public String getRangeString(String key, String column, Object range, String rawSQL) throws Exception { @@ -3535,24 +4148,25 @@ else if (range instanceof String) {//非Number类型需要客户端拼接成 < ' List raw = getRaw(); boolean containRaw = raw == null ? false : raw.contains(key); String lk = logic.isAnd() ? AND : OR; - + for (int i = 0; i < cs.length; i++) {//对函数条件length(key)<=5这种不再在开头加key String expr = cs[i]; - + if (expr.length() > 100) { throw new UnsupportedOperationException(key + ":value 的 value 中字符串 " + expr + " 不合法!" + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); } - + int index = expr == null ? -1 : expr.indexOf("("); if (index >= 0) { - expr = parseSQLExpression(key, expr, containRaw, false, key + ":\"!=null;+3*2<=10;function0(arg0,arg1,...)>1;function1(...)%5<=3...\""); - } + 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)) { @@ -3563,9 +4177,10 @@ else if ("!=null".equals(c)) { } 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 + " !不允许连续减号 -- !不允许空格!"); + + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null " + + "或 符合正则表达式 " + PATTERN_RANGE + " !不允许连续减号 -- !不允许空格!"); } - + expr += (j <= 0 ? "" : lk) + fk + c; } } @@ -3573,14 +4188,15 @@ else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() condition += ((i <= 0 ? "" : lk) + expr); } } - + if (condition.isEmpty()) { return ""; } return getCondition(logic.isNot(), condition); } - else if (range instanceof Subquery) { //如果在 Parser 解析成 SQL 字符串再引用,没法保证安全性,毕竟可以再通过远程函数等方式来拼接再替代,最后引用的字符串就能注入 + else if (range instanceof Subquery) { + // 如果在 Parser 解析成 SQL 字符串再引用,没法保证安全性,毕竟可以再通过远程函数等方式来拼接再替代,最后引用的字符串就能注入 return getKey(k) + (logic.isNot() ? NOT : "") + " IN " + getSubqueryString((Subquery) range); } @@ -3590,7 +4206,7 @@ else if (range instanceof Subquery) { //如果在 Parser 解析成 SQL 字符串 /**WHERE key IN ('key0', 'key1', ... ) * @param in * @return IN ('key0', 'key1', ... ) - * @throws NotExistException + * @throws NotExistException */ @JSONField(serialize = false) public String getInString(String key, String column, Object[] in, boolean not) throws NotExistException { @@ -3610,7 +4226,8 @@ public String getInString(String key, String column, Object[] in, boolean not) t //}{ exists <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**WHERE EXISTS subquery - * 如果合并到 getRangeString,一方面支持不了 [1,2,2] 和 ">1" (转成 EXISTS(SELECT IN ) 需要static newSQLConfig,但它不能传入子类实例,除非不是 static),另一方面多了子查询临时表性能会比 IN 差 + * 如果合并到 getRangeString,一方面支持不了 [1,2,2] 和 ">1" (转成 EXISTS(SELECT IN ) 需要 + * static newSQLConfig,但它不能传入子类实例,除非不是 static),另一方面多了子查询临时表性能会比 IN 差 * @param key * @param value * @return EXISTS ALL(SELECT ...) @@ -3619,7 +4236,8 @@ public String getInString(String key, String column, Object[] in, boolean not) t @JSONField(serialize = false) 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 !"); + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!" + + "@raw 不支持 key}{ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } if (value == null) { return ""; @@ -3641,13 +4259,14 @@ public String getExistsString(String key, String column, Object value, String ra /**WHERE key contains value * @param key * @param value - * @return {@link #getContainString(String, Object[], int)} + * @return {@link #getContainString(String, String, Object[], int)} * @throws NotExistException */ @JSONField(serialize = false) 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 !"); + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key<> 这种功能符 !" + + "只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } Logic logic = new Logic(column); @@ -3656,13 +4275,15 @@ public String getContainString(String key, String column, Object value, String r return getContainString(key, column, newJSONArray(value).toArray(), logic.getType()); } - /**WHERE key contains childs TODO 支持 key<>: { "path":"$[0].name", "value": 82001 } 或者 key<$[0].name>:82001 或者 key$[0].name<>:82001 ? 还是前者好,key 一旦复杂了,包含 , ; : / [] 等就容易和 @combine 其它功能等冲突 + /**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 |, &, ! * @return LOGIC [ ( key LIKE '[" + childs[i] + "]' OR key LIKE '[" + childs[i] + ", %' * OR key LIKE '%, " + childs[i] + ", %' OR key LIKE '%, " + childs[i] + "]' ) ] - * @throws IllegalArgumentException + * @throws IllegalArgumentException */ @JSONField(serialize = false) public String getContainString(String key, String column, Object[] childs, int type) throws IllegalArgumentException { @@ -3679,36 +4300,48 @@ public String getContainString(String key, String column, Object[] childs, int t 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 !"); + 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] 中的任何一个 !"); + throw new IllegalArgumentException(key + ":{ path:path, value:value } 中 value 类型" + + "不能为 [JSONObject, JSONArray, Collection, Map] 中的任何一个 !"); } } 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 + "]"); + if (isPostgreSQL() || isCockroachDB() || isInfluxDB()) { + condition += (getKey(column) + " @> " + getValue(key, column, newJSONArray(c))); + // operator does not exist: jsonb @> character varying "[" + c + "]"); + } + else if (isOracle() || isDameng() || isKingBase()) { + condition += ("json_textcontains(" + getKey(column) + ", " + (StringUtil.isEmpty(path, true) + ? "'$'" : getValue(key, column, path)) + ", " + getValue(key, column, c == null ? null : c.toString()) + ")"); } - 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 if (isPresto() || isTrino()) { + condition += ("json_array_contains(cast(" + getKey(column) + " AS VARCHAR), " + + getValue(key, column, c) + (StringUtil.isEmpty(path, true) + ? "" : ", " + getValue(key, column, path)) + ")"); } 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)) + ")"); + 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)) + ")"); } } } if (condition.isEmpty()) { condition = getKey(column) + SQL.isNull(true) + OR + getLikeString(key, column, "[]"); // key = '[]' 无结果! - } + } else { condition = getKey(column) + SQL.isNull(false) + AND + "(" + condition + ")"; } @@ -3725,6 +4358,52 @@ else if (isOracle()) { //key@:{} Subquery <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + /** + * 只要 method != RequestMethod.POST 就都支持 with-as表达式 + * @param cfg + * @param subquery + * @return + * @throws Exception + */ + private String withAsExpreSubqueryString(SQLConfig cfg, Subquery subquery) throws Exception { + boolean isWithAsEnable = isWithAsEnable(); + List list = isWithAsEnable ? getWithAsExprSqlList() : null; + if (cfg.getMethod() != RequestMethod.POST && list == null) { + clearWithAsExprListIfNeed(); + } + + String quote = getQuote(); + String as = getAs(); + + String withAsExpreSql; + if (list != null) { + String withQuoteName = quote + subquery.getKey() + quote; + list.add(" " + withQuoteName + as + "(" + cfg.getSQL(isPrepared()) + ") "); + withAsExpreSql = " SELECT * FROM " + withQuoteName; + + // 预编译参数 FIXME 这里重复添加了,导致子查询都报错参数超过 ? 数量 Parameter index out of range (5 > number of parameters, which is 4) + List subPvl = cfg.getPreparedValueList(); + if (subPvl != null && subPvl.isEmpty() == false) { + List valueList = getWithAsExprPreparedValueList(); + if (valueList == null) { + valueList = new ArrayList<>(); + } + valueList.addAll(subPvl); + setWithAsExprPreparedValueList(valueList); + + cfg.setPreparedValueList(new ArrayList<>()); + } + } else { + withAsExpreSql = cfg.getSQL(isPrepared()); + // mysql 才存在这个问题, 主表和子表是一张表 + if (isWithAsEnable && isMySQL() && StringUtil.equals(getTable(), subquery.getFrom())) { + withAsExpreSql = " SELECT * FROM (" + withAsExpreSql + ")" + as + quote + subquery.getKey() + quote; + } + } + + return withAsExpreSql; + } + @Override public String getSubqueryString(Subquery subquery) throws Exception { if (subquery == null) { @@ -3734,10 +4413,39 @@ public String getSubqueryString(Subquery subquery) throws Exception { String range = subquery.getRange(); SQLConfig cfg = subquery.getConfig(); + // 子查询 = 主语句 datasource + if(StringUtil.equals(this.getTable(), subquery.getFrom() ) == false && cfg.hasJoin() == false) { + cfg.setDatasource(this.getDatasource()); + } cfg.setPreparedValueList(new ArrayList<>()); - String sql = (range == null || range.isEmpty() ? "" : range) + "(" + cfg.getSQL(isPrepared()) + ") "; + String withAsExprSql = withAsExpreSubqueryString(cfg, subquery); + String sql = (range == null || range.isEmpty() ? "" : range) + "(" + withAsExprSql + ") "; + + //// SELECT .. FROM(SELECT ..) .. WHERE .. 格式需要把子查询中的预编译值提前 + //// 如果外查询 SELECT concat(`name`,?) 这种 SELECT 里也有预编译值,那就不能这样简单反向 + //List subPvl = cfg.getPreparedValueList(); + //if (subPvl != null && subPvl.isEmpty() == false) { + // List pvl = getPreparedValueList(); + // + // if (pvl != null && pvl.isEmpty() == false) { + // subPvl.addAll(pvl); + // } + // setPreparedValueList(subPvl); + //} + + List subPvl = cfg.getPreparedValueList(); + if (subPvl != null && subPvl.isEmpty() == false) { + List pvl = getPreparedValueList(); + + if (pvl == null || pvl.isEmpty()) { + pvl = subPvl; + } + else { + pvl.addAll(subPvl); + } - preparedValueList.addAll(cfg.getPreparedValueList()); + setPreparedValueList(pvl); + } return sql; } @@ -3757,7 +4465,7 @@ public static String getCondition(boolean not, String condition) { /**拼接条件 * @param not * @param condition - * @param outerBreaket + * @param addOuterBracket * @return */ public static String getCondition(boolean not, String condition, boolean addOuterBracket) { @@ -3767,7 +4475,7 @@ public static String getCondition(boolean not, String condition, boolean addOute /**转为JSONArray - * @param tv + * @param obj * @return */ @NotNull @@ -3789,7 +4497,7 @@ public static JSONArray newJSONArray(Object obj) { //SET <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**获取SET * @return - * @throws Exception + * @throws Exception */ @JSONField(serialize = false) public String getSetString() throws Exception { @@ -3831,8 +4539,10 @@ public String getSetString(RequestMethod method, Map content, bo value = entry.getValue(); String column = getRealKey(method, key, false, true, verifyName); - setString += (isFirst ? "" : ", ") + (getKey(column) + " = " + (keyType == 1 ? getAddString(key, column, value) : (keyType == 2 - ? getRemoveString(key, column, value) : getValue(key, column, value)) ) ); + setString += (isFirst ? "" : ", ") + (getKey(column) + " = " + + (keyType == 1 ? getAddString(key, column, value) : (keyType == 2 + ? getRemoveString(key, column, value) : getValue(key, column, value)) ) + ); isFirst = false; } @@ -3841,7 +4551,7 @@ public String getSetString(RequestMethod method, Map content, bo if (setString.isEmpty()) { throw new IllegalArgumentException("PUT 请求必须在Table内设置要修改的 key:value !"); } - return (isClickHouse()?" ":" SET ") + setString; + return (isClickHouse() ? " " : " SET ") + setString; } /**SET key = concat(key, 'value') @@ -3872,26 +4582,43 @@ public String getRemoveString(String key, String column, Object value) throws Il return getKey(column) + " - " + value; } if (value instanceof String) { - return SQL.replace(getKey(column), (String) getValue(key, column, value), "''");// " replace(" + column + ", '" + value + "', '') "; + return SQL.replace(getKey(column), (String) getValue(key, column, value), "''"); + // " replace(" + column + ", '" + value + "', '') "; } throw new IllegalArgumentException(key + ":value 中 value 类型错误,必须是 Number,String,Array 中的任何一种!"); } //SET >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + @Override + public boolean isFakeDelete() { + return false; + } + + @Override + public Map onFakeDelete(Map map) { + return map; + } /** * @return - * @throws Exception + * @throws Exception */ @JSONField(serialize = false) @Override public String getSQL(boolean prepared) throws Exception { - return getSQL(this.setPrepared(prepared)); + boolean isPrepared = isPrepared(); + if (isPrepared == prepared) { + return getSQL(this); + } + + String sql = getSQL(this.setPrepared(prepared)); + setPrepared(isPrepared); + return sql; } /** * @param config * @return - * @throws Exception + * @throws Exception */ public static String getSQL(AbstractSQLConfig config) throws Exception { if (config == null) { @@ -3899,14 +4626,20 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { return null; } - //TODO procedure 改为 List procedureList; behind : true; function: callFunction(); String key; ... + // TODO procedure 改为 List procedureList; behind : true; function: callFunction(); String key; ... // for (...) { Call procedure1();\n SQL \n; Call procedure2(); ... } - // 貌似不需要,因为 ObjecParser 里就已经处理的顺序等,只是这里要解决下 Schema 问题。 + // 貌似不需要,因为 ObjectParser 里就已经处理的顺序等,只是这里要解决下 Schema 问题。 + + String procedure = config.getProcedure(); + if (StringUtil.isNotEmpty(procedure, true)) { + int ind = procedure.indexOf("."); + boolean hasPrefix = ind >= 0 && ind < procedure.indexOf("("); + String sch = hasPrefix ? AbstractFunctionParser.extractSchema( + procedure.substring(0, ind), config.getTable() + ) : config.getSQLSchema(); - String sch = config.getSQLSchema(); - if (StringUtil.isNotEmpty(config.getProcedure(), true)) { String q = config.getQuote(); - return "CALL " + q + sch + q + "."+ config.getProcedure(); + return "CALL " + q + sch + q + "." + (hasPrefix ? procedure.substring(ind + 1) : procedure); } String tablePath = config.getTablePath(); @@ -3915,77 +4648,125 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { return null; } + // 解决重复添加导致报错:Parameter index out of range (6 > number of parameters, which is 5) + config.setPreparedValueList(new ArrayList<>()); + String cSql = null; switch (config.getMethod()) { - case POST: - 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 "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); - case DELETE: - 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)) { // 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_COUNT + 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. - //针对oracle分组后条数的统计 - 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(); + case POST: + return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString(); + case PUT: + if(config.isClickHouse()){ + return "ALTER TABLE " + tablePath + " UPDATE" + config.getSetString() + config.getWhereString(true); + } + cSql = "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + + (config.isMySQL() ? config.getLimitString() : ""); + cSql = buildWithAsExprSql(config, cSql); + return cSql; + case DELETE: + if(config.isClickHouse()){ + return "ALTER TABLE " + tablePath + " DELETE" + config.getWhereString(true); + } + cSql = "DELETE FROM " + tablePath + config.getWhereString(true) + + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT + cSql = buildWithAsExprSql(config, cSql); + return cSql; + default: + String explain = config.isExplain() ? (config.isSQLServer() ? "SET STATISTICS PROFILE ON " + : (config.isOracle() || config.isDameng() || config.isKingBase() ? "EXPLAIN PLAN FOR " : "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) + + config.getAs() + q + JSONResponse.KEY_COUNT + q + config.getLimitString(); } - - String sql = "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); - return explain + config.getOraclePageSql(sql); + + config.setPreparedValueList(new ArrayList()); + String column = config.getColumnString(); + if (config.isOracle() || config.isDameng() || config.isKingBase()) { + //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. + //针对oracle分组后条数的统计 + 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(tablePath, config) + ") " + config.getLimitString(); + } + + String sql = "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM + ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(tablePath, config); + return explain + config.getOraclePageSql(sql); + } + + cSql = "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + + column + " FROM " + getConditionString(tablePath, config) + config.getLimitString(); + cSql = buildWithAsExprSql(config, cSql); + if(config.isElasticsearch()) { // elasticSearch 不支持 explain + return cSql; + } + return explain + cSql; + } + } + + private static String buildWithAsExprSql(@NotNull AbstractSQLConfig config, String cSql) throws Exception { + if (config.isWithAsEnable() == false) { + return cSql; + } + + List list = config.getWithAsExprSqlList(); + int size = list == null ? 0 : list.size(); + if (size > 0) { + String withAsExpreSql = "WITH "; + for (int i = 0; i < size; i++) { + withAsExpreSql += (i <= 0 ? "" : ",") + list.get(i) + "\n"; } - - return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); + cSql = withAsExpreSql + cSql; + config.clearWithAsExprListIfNeed(); } + return cSql; + } + + @Override + public boolean isWithAsEnable() { + return ENABLE_WITH_AS && (isMySQL() == false || getDBVersionNums()[0] >= 8); } /**Oracle的分页获取 - * @param config * @param sql * @return */ protected String getOraclePageSql(String sql) { int count = getCount(); + if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { // TODO HEAD 真的不需要 LIMIT ? + return sql; + } 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; + String quote = getQuote(); + String alias = quote + getSQLAlias() + quote; + return "SELECT * FROM (SELECT " + alias + ".*, ROWNUM "+ quote + "RN" + quote +" FROM (" + sql + ") " + alias + + " WHERE ROWNUM <= " + (offset + count) + ") WHERE "+ quote + "RN" + quote +" > " + offset; } - + /**获取条件SQL字符串 - * @param column * @param table * @param config * @return - * @throws Exception + * @throws Exception */ - private static String getConditionString(String column, String table, AbstractSQLConfig config) throws Exception { - String where = config.getWhereString(true); - + private static String getConditionString(String table, AbstractSQLConfig config) throws Exception { Subquery from = config.getFrom(); if (from != null) { - table = config.getSubqueryString(from) + " AS " + config.getAliasWithQuote() + " "; + table = config.getSubqueryString(from) + config.getAs() + config.getSQLAliasWithQuote() + " "; } + String join = config.getJoinString(); + + String where = config.getWhereString(true); + //根据方法不同,聚合语句不同。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); + aggregation = config.getGroupString(true) + config.getHavingString(true) + + config.getOrderString(true); } - else 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) ; } else if (config.getMethod() == PUT || config.getMethod() == DELETE) { @@ -3995,7 +4776,7 @@ else if (config.getMethod() == PUT || config.getMethod() == DELETE) { aggregation = ""; } - String condition = table + config.getJoinString() + where + aggregation; + String condition = table + join + where + aggregation; ; //+ config.getLimitString(); //no need to optimize @@ -4040,7 +4821,7 @@ public boolean isKeyPrefix() { return keyPrefix; } @Override - public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { + public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { this.keyPrefix = keyPrefix; return this; } @@ -4051,8 +4832,8 @@ public String getJoinString() throws Exception { if (joinList != null) { String quote = getQuote(); - List pvl = new ArrayList<>(); - boolean changed = false; + List pvl = getPreparedValueList(); // new ArrayList<>(); + //boolean changed = false; // 主表不用别名 String ta; for (Join j : joinList) { @@ -4067,8 +4848,9 @@ public String getJoinString() throws Exception { // <"INNER JOIN User ON User.id = Moment.userId", UserConfig> TODO AS 放 getSQLTable 内 SQLConfig jc = j.getJoinConfig(); jc.setPrepared(isPrepared()); - - String jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); + // 将关联表所属数据源配置为主表数据源 + jc.setDatasource(this.getDatasource()); + String jt = jc.getSQLAlias(); List onList = j.getOnList(); //如果要强制小写,则可在子类重写这个方法再 toLowerCase @@ -4089,18 +4871,18 @@ 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; + + " JOIN ( " + jc.getSQL(isPrepared()) + " ) " + getAs() + quote + jt + quote; sql = concatJoinOn(sql, quote, j, jt, onList); jc.setMain(false).setKeyPrefix(true); pvl.addAll(jc.getPreparedValueList()); - changed = true; + //changed = true; break; - case "&": // INNER JOIN: A & B - case "": // FULL JOIN: A | B - case "|": // FULL JOIN: A | B + case "&": // INNER JOIN: A & B + case "": // FULL JOIN: A | B + case "|": // FULL JOIN: A | B case "!": // OUTER JOIN: ! (A | B) case "^": // SIDE JOIN: ! (A & B) case "(": // ANTI JOIN: A & ! B @@ -4109,9 +4891,10 @@ public String getJoinString() throws Exception { sql = concatJoinOn(sql, quote, j, jt, onList); break; default: + String k = jc.getTableKey(); throw new UnsupportedOperationException( - "join:value 中 value 里的 " + jt + "/" + j.getPath() - + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + "join:value 中 value 里的 " + k + "/" + j.getPath() + + "错误!不支持 " + k + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" ); } @@ -4125,17 +4908,20 @@ public String getJoinString() throws Exception { ow = oc.getWhereString(false); pvl.addAll(oc.getPreparedValueList()); - changed = true; + //changed = true; } joinOns += " \n " + sql + (StringUtil.isEmpty(ow, true) ? "" : " AND ( " + ow + " ) "); } - if (changed) { - pvl.addAll(preparedValueList); - preparedValueList = pvl; - } + //if (changed) { + // List opvl = getPreparedValueList(); + // if (opvl != null && opvl.isEmpty() == false) { + // pvl.addAll(opvl); + // } + setPreparedValueList(pvl); + //} } @@ -4143,32 +4929,42 @@ public String getJoinString() throws Exception { } - protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNull Join j, @NotNull String jt, List onList) { + protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNull Join join, @NotNull String jt, List onList) { if (onList != null) { + SQLConfig jc = join.getJoinConfig(); + Map castMap = jc == null ? null : jc.getCast(); + 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); + onJoinNotRelation(sql, quote, join, jt, onList, on); + } + + String lk = quote + jt + quote + "." + quote + on.getKey() + quote; + Object ct = castMap == null ? null : castMap.get(on.getOriginKey()); + if (StringUtil.isNotEmpty(ct, false)) { + lk = "cast(" + lk + " AS " + ct + ")"; // 解决 JOIN ON 不支持 @cast 问题,CAST(expression AS TYPE) 中 AS 不能省略 } String rt = on.getRelateType(); + + String rk = quote + SQLConfig.getSQLAlias(on.getTargetTable(), on.getTargetAlias()) + quote + "." + quote + on.getTargetKey() + quote; + 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; + sql += (first ? ON : AND) + lk + (isNot ? " != " : " = ") + rk; } else { - onJoinComplextRelation(sql, quote, j, jt, onList, on); + onJoinComplexRelation(sql, quote, join, jt, onList, on); if (">=".equals(rt) || "<=".equals(rt) || ">".equals(rt) || "<".equals(rt)) { if (isNot) { - throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + join.getPath() + " 中 JOIN ON 条件关联逻辑符 " + rt + " 不合法! >, <, >=, <= 不支持与或非逻辑符 & | ! !"); } - sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " " + rt + " " - + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + sql += (first ? ON : AND) + lk + " " + rt + " " + rk; } else if (rt.endsWith("$")) { String t = rt.substring(0, rt.length() - 1); @@ -4180,7 +4976,8 @@ else if (rt.endsWith("$")) { if (t.isEmpty()) { if (r == '?') { - throw new IllegalArgumentException(on.getOriginKey() + ":value 中字符 " + on.getOriginKey() + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); + throw new IllegalArgumentException(on.getOriginKey() + ":value 中字符 " + on.getOriginKey() + + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); } l = r; @@ -4189,7 +4986,8 @@ else if (rt.endsWith("$")) { l = t.charAt(t.length() - 1); if (l == '%' || l == '_' || l == '?') { if (l == r) { - throw new IllegalArgumentException(on.getOriginKey() + ":value 中字符 " + t + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); + throw new IllegalArgumentException(on.getOriginKey() + + ":value 中字符 " + t + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); } t = t.substring(0, t.length() - 1); @@ -4211,36 +5009,38 @@ else if (l > 0 && StringUtil.isName(String.valueOf(l))) { } 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; + sql += (first ? ON : AND) + lk + (isNot ? NOT : "") + " LIKE " + rk; } 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 + "')"); + sql += (first ? ON : AND) + lk + (isNot ? NOT : "") + + (l <= 0 ? " LIKE concat(" : " LIKE concat('" + l + "', ") + rk + (r <= 0 ? ")" : ", '" + r + "')"); } } 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; + if (isPostgreSQL() || isCockroachDB() || isInfluxDB()) { + sql += (first ? ON : AND) + lk + (isNot ? NOT : "") + " ~" + (ignoreCase ? "* " : " ") + rk; } - 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 (isOracle() || isDameng() || isKingBase()) { + sql += (first ? ON : AND) + "regexp_like(" + lk + ", " + rk + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; + } + else if (isPresto() || isTrino()) { + sql += (first ? ON : AND) + "regexp_like(" + (ignoreCase ? "lower(" : "") + lk + (ignoreCase ? ")" : "") + + ", " + (ignoreCase ? "lower(" : "") + rk + (ignoreCase ? ")" : "") + ")"; } 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 ? ")" : "") + ")"; + sql += (first ? ON : AND) + "match(" + (ignoreCase ? "lower(" : "") + lk + (ignoreCase ? ")" : "") + + ", " + (ignoreCase ? "lower(" : "") + rk + (ignoreCase ? ")" : "") + ")"; + } + else if (isElasticsearch()) { + sql += (first ? ON : AND) + lk + (isNot ? NOT : "") + " RLIKE " + rk; } 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 ? ")" : ""); + sql += (first ? ON : AND) + (ignoreCase ? "lower(" : "") + lk + (ignoreCase ? ")" : "") + + " REGEXP " + (ignoreCase ? "lower(" : "") + rk + (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; + sql += (first ? ON : AND) + lk + (isNot ? NOT : "") + " REGEXP " + (ignoreCase ? "" : "BINARY ") + rk; } } else if ("{}".equals(rt) || "<>".equals(rt)) { @@ -4248,13 +5048,13 @@ else if ("{}".equals(rt) || "<>".equals(rt)) { String ta = on.getTargetAlias(); Map cast = null; - if (tt.equals(getTable()) && ((ta == null && getAlias() == null) || ta.equals(getAlias()))) { + if (tt.equals(getTable()) && Objects.equals(ta, 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()))) { + if (tt.equals(jn.getTable()) && Objects.equals(ta, jn.getAlias())) { cast = getCast(); find = true; break; @@ -4262,33 +5062,31 @@ else if ("{}".equals(rt) || "<>".equals(rt)) { } if (find == false) { - throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + join.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; - } + boolean isIn = "{}".equals(rt); + String arrKeyPath = isIn ? rk : lk; + String itemKeyPath = isIn ? lk : rk; - if (isPostgreSQL()) { //operator does not exist: jsonb @> character varying "[" + c + "]"); + if (isPostgreSQL() || isCockroachDB() || isInfluxDB()) { //operator does not exist: jsonb @> character varying "[" + c + "]"); sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath - + " IS NOT NULL AND " + arrKeyPath + " @> " + itemKeyPath) + (isNot ? ") " : ""); + + " IS NOT NULL AND " + arrKeyPath + " @> " + itemKeyPath) + (isNot ? ") " : ""); } - else if (isOracle()) { + else if (isOracle() || isDameng() || isKingBase()) { sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + " IS NOT NULL AND json_textcontains(" + arrKeyPath + ", '$', " + itemKeyPath + ")") + (isNot ? ") " : ""); } + else if (isPresto() || isTrino()) { + sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + + " IS NOT NULL AND json_array_contains(cast(" + arrKeyPath + + " AS VARCHAR), " + itemKeyPath + ")") + (isNot ? ") " : ""); + } else if (isClickHouse()) { sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + " IS NOT NULL AND has(JSONExtractArrayRaw(assumeNotNull(" + arrKeyPath + "))" @@ -4299,12 +5097,12 @@ else if (isClickHouse()) { + " IS NOT NULL AND json_contains(" + arrKeyPath + (isBoolOrNum ? ", cast(" + itemKeyPath + " AS CHAR), '$')" : ", concat('\"', " + itemKeyPath + ", '\"'), '$')" - ) - ) + (isNot ? ") " : ""); + ) + ) + (isNot ? ") " : ""); } } else { - throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + join.getPath() + " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, >, <, >=, <=, !=, $, ~, {}, <> 这几种!"); } } @@ -4316,16 +5114,17 @@ else if (isClickHouse()) { return sql; } - protected void onJoinNotRelation(String sql, String quote, Join j, String jt, List onList, On on) { + protected void onJoinNotRelation(String sql, String quote, Join join, String table, 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 onJoinComplexRelation(String sql, String quote, Join join, String table, List onList, On on) { + throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !" + + "性能很差、需求极少,默认只允许 = 等价关联,如要取消禁用可在后端重写相关方法!"); } - protected void onGetJoinString(Join j) throws UnsupportedOperationException { + protected void onGetJoinString(Join join) throws UnsupportedOperationException { } - protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { + protected void onGetCrossJoinString(Join join) throws UnsupportedOperationException { throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } @@ -4336,55 +5135,61 @@ protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException * @param isProcedure * @param callback * @return - * @throws Exception + * @throws Exception */ - public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure, Callback callback) throws Exception { + 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 + " !"); + Boolean explain = request.getBoolean(KEY_EXPLAIN); + if (explain != null && 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()) + "] 中的一种!"); + throw new UnsupportedDataTypeException("@database:value 中 value 错误,只能是 [" + + StringUtil.getString(DATABASE_LIST.toArray()) + "] 中的一种!"); } - String schema = request.getString(KEY_SCHEMA); String datasource = request.getString(KEY_DATASOURCE); + String namespace = request.getString(KEY_NAMESPACE); + String catalog = request.getString(KEY_CATALOG); + String schema = request.getString(KEY_SCHEMA); - SQLConfig config = callback.getSQLConfig(method, database, schema, datasource, table); + SQLConfig config = callback.getSQLConfig(method, database, schema, datasource, table); config.setAlias(alias); - config.setDatabase(database); //不删,后面表对象还要用的,必须放在 parseJoin 前 - config.setSchema(schema); //不删,后面表对象还要用的 - config.setDatasource(datasource); //不删,后面表对象还要用的 + config.setDatabase(database); // 不删,后面表对象还要用的,必须放在 parseJoin 前 + config.setDatasource(datasource); // 不删,后面表对象还要用的 + config.setNamespace(namespace); // 不删,后面表对象还要用的 + config.setCatalog(catalog); // 不删,后面表对象还要用的 + config.setSchema(schema); // 不删,后面表对象还要用的 if (isProcedure) { return config; } - config = parseJoin(method, config, joinList, callback); //放后面会导致主表是空对象时 joinList 未解析 + config = parseJoin(method, config, joinList, callback); // 放后面会导致主表是空对象时 joinList 未解析 if (request.isEmpty()) { // User:{} 这种空内容在查询时也有效 - return config; //request.remove(key); 前都可以直接return,之后必须保证 put 回去 + return config; // request.remove(key); 前都可以直接return,之后必须保证 put 回去 } - //对 id, id{}, userId, userId{} 处理,这些只要不为 null 就一定会作为 AND 条件 <<<<<<<<<<<<<<<<<<<<<<<<< + // 对 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 + "{}"; - Object idIn = request.get(idInKey); //可能是 id{}:">0" + Object idIn = request.get(idInKey); // 可能是 id{}:">0" if (idIn instanceof Collection) { // 排除掉 0, 负数, 空字符串 等无效 id 值 Collection ids = (Collection) idIn; List newIdIn = new ArrayList<>(); - for (Object d : ids) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! + 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))) { if (newIdIn.contains(d) == false) { newIdIn.add(d); @@ -4406,14 +5211,14 @@ public static SQLConfig newSQLConfig(RequestMethod method, St id = callback.newId(method, database, schema, datasource, table); // null 表示数据库自增 id } - if (id != null) { //null无效 - if (id instanceof Number) { - if (((Number) id).longValue() <= 0) { //一定没有值 + 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)) { //一定没有值 + if (StringUtil.isEmpty(id, true)) { // 一定没有值 throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".id, true)"); } } @@ -4422,16 +5227,16 @@ else if (id instanceof Subquery) {} throw new IllegalArgumentException(idKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); } - if (idIn instanceof Collection) { //共用idIn场景少性能差 + if (idIn instanceof Collection) { // 共用idIn场景少性能差 boolean contains = false; Collection idList = ((Collection) idIn); - for (Object d : idList) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! + for (Object d : idList) { // 不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! if (d != null && id.toString().equals(d.toString())) { contains = true; break; } } - if (contains == false) {//empty有效 BaseModel.isEmpty(idIn) == false) { + if (contains == false) { // empty有效 BaseModel.isEmpty(idIn) == false) { throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); } } @@ -4441,12 +5246,12 @@ else if (id instanceof Subquery) {} } } - //对 userId 和 userId{} 处理,这两个一定会作为条件 - Object userIdIn = userIdInKey.equals(idInKey) ? null : request.get(userIdInKey); //可能是 userId{}:">0" + // 对 id, id{}, userId, userId{} 处理,这些只要不为 null 就一定会作为 AND 条件 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + 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<>(); - for (Object d : userIds) { //不用 userIdIn.contains(userId) 因为 userIdIn 里存到很可能是 Integer,userId 又是 Long! + 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); @@ -4460,14 +5265,14 @@ else if (id instanceof Subquery) {} } Object userId = userIdKey.equals(idKey) ? null : request.get(userIdKey); - if (userId != null) { //null无效 - if (userId instanceof Number) { - if (((Number) userId).longValue() <= 0) { //一定没有值 + 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)) { //一定没有值 + if (StringUtil.isEmpty(userId, true)) { // 一定没有值 throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".userId, true)"); } } @@ -4476,22 +5281,21 @@ else if (userId instanceof Subquery) {} throw new IllegalArgumentException(userIdKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); } - if (userIdIn instanceof Collection) { //共用userIdIn场景少性能差 + if (userIdIn instanceof Collection) { // 共用 userIdIn 场景少性能差 boolean contains = false; Collection userIds = (Collection) userIdIn; - for (Object d : userIds) { //不用 userIdIn.contains(userId) 因为 userIdIn 里存到很可能是 Integer,userId 又是 Long! + 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) { + 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 条件 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + // 对 id, id{}, userId, userId{} 处理,这些只要不为 null 就一定会作为 AND 条件 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> String role = request.getString(KEY_ROLE); @@ -4505,21 +5309,25 @@ else if (userId instanceof Subquery) {} Object having = request.get(KEY_HAVING); String havingAnd = request.getString(KEY_HAVING_AND); String order = request.getString(KEY_ORDER); + Object keyMap = request.get(KEY_KEY); String raw = request.getString(KEY_RAW); String json = request.getString(KEY_JSON); + String mthd = request.getString(KEY_METHOD); try { - //强制作为条件且放在最前面优化性能 + // 强制作为条件且放在最前面优化性能 request.remove(idKey); request.remove(idInKey); request.remove(userIdKey); request.remove(userIdInKey); - //关键词 + // 关键词 request.remove(KEY_ROLE); request.remove(KEY_EXPLAIN); request.remove(KEY_CACHE); - request.remove(KEY_DATASOURCE); request.remove(KEY_DATABASE); + request.remove(KEY_DATASOURCE); + request.remove(KEY_NAMESPACE); + request.remove(KEY_CATALOG); request.remove(KEY_SCHEMA); request.remove(KEY_FROM); request.remove(KEY_COLUMN); @@ -4530,8 +5338,10 @@ else if (userId instanceof Subquery) {} request.remove(KEY_HAVING); request.remove(KEY_HAVING_AND); request.remove(KEY_ORDER); + request.remove(KEY_KEY); request.remove(KEY_RAW); request.remove(KEY_JSON); + request.remove(KEY_METHOD); // @null <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @@ -4542,7 +5352,8 @@ else if (userId instanceof Subquery) {} 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); @@ -4559,13 +5370,16 @@ else if (userId instanceof Subquery) {} 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() + "' 不合法!不允许为空!"); + 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() + "' 不合法!必须符合类型名称格式!"); + 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() + "' 已存在!不允许重复设置类型!"); + throw new IllegalArgumentException(table + ":{} 里的 @cast: 'key0:type0,key1:type1..' 中 '" + + c + "' 对应的 key 的字符 '" + p.getKey() + "' 已存在!不允许重复设置类型!"); } castMap.put(p.getKey(), p.getValue()); @@ -4577,11 +5391,17 @@ else if (userId instanceof Subquery) {} 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... + Map tableWhere = new LinkedHashMap(); // 保证顺序好优化 WHERE id > 1 AND name LIKE... - //已经remove了id和id{},以及@key - Set set = request.keySet(); //前面已经判断request是否为空 - if (method == POST) { //POST操作 + boolean ignoreBlankStr = IGNORE_BLANK_STRING_METHOD_LIST != null && IGNORE_BLANK_STRING_METHOD_LIST.contains(method); + boolean ignoreEmptyStr = ignoreBlankStr || (IGNORE_EMPTY_STRING_METHOD_LIST != null && IGNORE_EMPTY_STRING_METHOD_LIST.contains(method)); + boolean ignoreEmptyOrBlankStr = ignoreEmptyStr || ignoreBlankStr; + + boolean enableFakeDelete = config.isFakeDelete(); + + // 已经 remove了 id 和 id{},以及 @key + Set set = request.keySet(); // 前面已经判断 request 是否为空 + if (method == POST) { // POST操作 if (idIn != null) { throw new IllegalArgumentException(table + ":{" + idInKey + ": value} 里的 key 不合法!POST 请求中不允许传 " + idInKey + " 这种非字段命名 key !必须为 英文字母 开头且只包含 英文字母、数字、下划线的 字段命名!"); } @@ -4589,14 +5409,14 @@ else if (userId instanceof Subquery) {} throw new IllegalArgumentException(table + ":{" + userIdInKey + ": value} 里的 key 不合法!POST 请求中不允许传 " + userIdInKey + " 这种非字段命名 key !必须为 英文字母 开头且只包含 英文字母、数字、下划线的 字段命名!"); } - if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 + 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(); @@ -4606,8 +5426,9 @@ else if (userId instanceof Subquery) {} throw new Exception("服务器内部错误:\n" + TAG + " newSQLConfig values == null || values.length != columns.length !"); } - - column = (id == null ? "" : idKey + ",") + (userId == null ? "" : userIdKey + ",") + StringUtil.getString(columns); //set已经判断过不为空 + + column = (id == null ? "" : idKey + ",") + (userId == null ? "" : userIdKey + ",") + + StringUtil.getString(columns); //set已经判断过不为空 int idCount = id == null ? (userId == null ? 0 : 1) : (userId == null ? 1 : 2); int size = idCount + columns.length; // 以 key 数量为准 @@ -4621,33 +5442,29 @@ else if (userId instanceof Subquery) {} } for (int j = 0; j < values.length; j++) { - items.add(values[j]); // 从第 1 个开始,允许 "null" + items.add(values[j]); // 从第 1 个开始,允许 "null" } List> valuess = new ArrayList<>(1); valuess.add(items); config.setValues(valuess); } - } - else { //非POST操作 - final boolean isWhere = method != PUT; //除了POST,PUT,其它全是条件!!! + } + else { // 非 POST 操作 + final boolean isWhere = method != PUT; // 除了POST,PUT,其它全是条件!!! - //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + // 条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 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); @@ -4669,33 +5486,65 @@ else if (userId instanceof Subquery) {} whereList.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; + if (enableFakeDelete) { + // 查询 Access 假删除 + Map accessFakeDeleteMap = method == DELETE + ? null : AbstractVerifier.ACCESS_FAKE_DELETE_MAP.get(config.getTable()); + Object deletedKey = accessFakeDeleteMap == null ? null : accessFakeDeleteMap.get(KEY_DELETED_KEY); + boolean hasKey = deletedKey instanceof String && StringUtil.isNotEmpty(deletedKey, true); + Object deletedValue = hasKey ? accessFakeDeleteMap.get(KEY_DELETED_VALUE) : null; + boolean containNotDeletedValue = hasKey ? accessFakeDeleteMap.containsKey(KEY_NOT_DELETED_VALUE) : false; + Object notDeletedValue = containNotDeletedValue ? accessFakeDeleteMap.get(KEY_NOT_DELETED_VALUE) : null; + + if (deletedValue != null || containNotDeletedValue) { + boolean isFakeDelete = true; + if (from != null) { + // 兼容 JOIN 外层 SELECT 重复生成 deletedKey + SQLConfig cfg = from.getConfig(); + if (cfg != null && StringUtil.equals(table, cfg.getTable())) { + isFakeDelete = false; } - 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 + "] 其中任何一个!"); + List jl = isFakeDelete && cfg != null ? cfg.getJoinList() : null; + if (jl != null) { + for (Join join : jl) { + if (join != null && StringUtil.equals(table, join.getTable())) { + isFakeDelete = false; + break; + } + } } + } - int newIndex = index + key.length() + 1; - if (str.length() <= newIndex) { - break; + if (isFakeDelete) { // 支持 deleted != 1 / deleted is null 等表达式 + if (deletedValue != null) { // deletedKey != deletedValue + String key = deletedKey + "!"; + tableWhere.put(key, deletedValue); + andList.add(key); + whereList.add(key); } - str = str.substring(newIndex); + + if (containNotDeletedValue) { // deletedKey = notDeletedValue + String key = deletedKey.toString(); + tableWhere.put(key, notDeletedValue); + andList.add(key); + whereList.add(key); + } + } + } + } + + if (StringUtil.isNotEmpty(combineExpr, true)) { + List banKeyList = Arrays.asList(idKey, idInKey, userIdKey, userIdInKey); + for (String key : banKeyList) { + if (isKeyInCombineExpr(combineExpr, key)) { + throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + key + " 不合法!" + + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); } } } else if (ws != null) { - for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 + for (int i = 0; i < ws.length; i++) { // 去除 &,|,! 前缀 String w = ws[i]; if (w != null) { if (w.startsWith("&")) { @@ -4735,37 +5584,46 @@ else if (w.startsWith("!")) { whereList.add(w); } - // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY - if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null + // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错! + // 去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 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); + if (config instanceof AbstractSQLConfig) { + ((AbstractSQLConfig) config).putWarnIfNeed(KEY_COMBINE, table + ":{} 里的 @combine:value 中的 value 里 " + + ws[i] + " 对应的条件 " + w + ":value 中 value 必须存在且不能为 null!"); + } } } } - //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + // 条件 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + Map tableContent = new LinkedHashMap(); - Object value; for (String key : set) { - value = request.get(key); + Object value = request.get(key); + if (ignoreEmptyOrBlankStr && value instanceof String && StringUtil.isEmpty(value, ignoreBlankStr)) { + continue; + } - if (key.endsWith("<>") == false && value instanceof Map) {//只允许常规Object - throw new IllegalArgumentException(table + ":{ " + key + ":value } 中 value 类型错误!除了 key<>:{} 外,不允许 " + 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出错 - if (isWhere || (StringUtil.isName(key.replaceFirst("[+-]$", "")) == false)) { + // 兼容 PUT @combine + // 解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 + if ((isWhere || (StringUtil.isName(key.replaceFirst("[+-]$", "")) == false)) + || (isWhere == false && StringUtil.isNotEmpty(combineExpr, true) && isKeyInCombineExpr(combineExpr, key))) { tableWhere.put(key, value); if (whereList.contains(key) == false) { andList.add(key); } - } - else if (whereList.contains(key)) { + } else if (whereList.contains(key)) { tableWhere.put(key, value); - } - else { - tableContent.put(key, value); //一样 instanceof JSONArray ? JSON.toJSONString(value) : value); + } else { + tableContent.put(key, value); // 一样 instanceof JSONArray ? JSON.toJSONString(value) : value); } } @@ -4780,6 +5638,28 @@ else if (whereList.contains(key)) { config.setContent(tableContent); } + if (enableFakeDelete && method == DELETE) { + // 查询 Access 假删除 + Map accessFakeDeleteMap = AbstractVerifier.ACCESS_FAKE_DELETE_MAP.get(config.getTable()); + + Object deletedKey = accessFakeDeleteMap.get(KEY_DELETED_KEY); + if (StringUtil.isNotEmpty(deletedKey, true)) { + // 假删除需要更新的其他字段,比如:删除时间 deletedTime 之类的 + Map fakeDeleteMap = new HashMap<>(); + fakeDeleteMap.put(deletedKey.toString(), accessFakeDeleteMap.get(KEY_DELETED_VALUE)); + fakeDeleteMap = config.onFakeDelete(fakeDeleteMap); + + Map content = config.getContent(); + if (content == null || content.isEmpty()) { + content = fakeDeleteMap; + } else { + content.putAll(fakeDeleteMap); + } + + config.setMethod(PUT); + config.setContent(content); + } + } List cs = new ArrayList<>(); @@ -4801,11 +5681,11 @@ else if (whereList.contains(key)) { } } - boolean distinct = column == null || rawColumnSQL != null ? false : column.startsWith(PREFFIX_DISTINCT); + boolean distinct = rawColumnSQL == null && column != null && column.startsWith(PREFIX_DISTINCT); if (rawColumnSQL == null) { - String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) + // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) + String[] fks = StringUtil.split(distinct ? column.substring(PREFIX_DISTINCT.length()) : column, ";"); if (fks != null) { - String[] ks; for (String fk : fks) { if (containColumnRaw) { String rawSQL = config.getRawSQL(KEY_COLUMN, fk); @@ -4818,8 +5698,8 @@ else if (whereList.contains(key)) { if (fk.contains("(")) { // fun0(key0,...) cs.add(fk); } - else { //key0,key1... - ks = StringUtil.split(fk); + else { // key0,key1... + String[] ks = StringUtil.split(fk); if (ks != null && ks.length > 0) { cs.addAll(Arrays.asList(ks)); } @@ -4857,7 +5737,7 @@ else if (whereList.contains(key)) { int start = havingsStr == null ? -1 : havingsStr.indexOf("("); int end = havingsStr == null ? -1 : havingsStr.lastIndexOf(")"); if (IS_HAVING_ALLOW_NOT_FUNCTION == false && (start < 0 || start >= end)) { - throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 中的第 " + i + + throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 中的第 " + i + " 个字符 '" + havingsStr + "' 不合法!里面没有包含 SQL 函数!必须为 fun(col1,col2..)?val 格式!"); } @@ -4899,6 +5779,9 @@ else if (newHaving instanceof JSONObject) { throw new IllegalArgumentException(table + ":{ " + havingKey + ":{ " + k + ":value } } 里的" + " value 不合法!类型只能是 String,且不允许为空!"); } + if (ignoreEmptyOrBlankStr && StringUtil.isEmpty(v, ignoreBlankStr)) { + continue; + } if (KEY_COMBINE.equals(k)) { havingCombine = (String) v; @@ -4918,8 +5801,29 @@ else if (newHaving != null) { } // @having, @haivng& >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + if (keyMap instanceof Map) { + config.setKeyMap((Map) keyMap); + } + else if (keyMap instanceof String) { + String[] ks = StringUtil.split((String) keyMap, ";"); + if (ks.length > 0) { + Map nkm = new LinkedHashMap<>(); + for (int i = 0; i < ks.length; i++) { + Entry ety = Pair.parseEntry(ks[i]); + if (ety == null) { + continue; + } + nkm.put(ety.getKey(), ety.getValue()); + } + config.setKeyMap(nkm); + } + } + else if (keyMap != null) { + throw new UnsupportedDataTypeException("@key:value 中 value 错误,只能是 String, JSONObject 中的一种!"); + } + - config.setExplain(explain); + config.setExplain(explain != null && explain); config.setCache(getCache(cache)); config.setDistinct(distinct); config.setColumn(column == null ? null : cs); //解决总是 config.column != null,总是不能得到 * @@ -4959,23 +5863,63 @@ else if (newHaving != null) { } // 关键词 - request.put(KEY_DATABASE, database); - 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_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_HAVING_AND, havingAnd); - request.put(KEY_ORDER, order); - request.put(KEY_RAW, raw); - request.put(KEY_JSON, json); + if (role != null) { + request.put(KEY_ROLE, role); + } + if (explain != null) { + request.put(KEY_EXPLAIN, explain); + } + if (cache != null) { + request.put(KEY_CACHE, cache); + } + if (database != null) { + request.put(KEY_DATABASE, database); + } + if (datasource != null) { + request.put(KEY_DATASOURCE, datasource); + } + if (schema != null) { + request.put(KEY_SCHEMA, schema); + } + if (from != null) { + request.put(KEY_FROM, from); + } + if (column != null) { + request.put(KEY_COLUMN, column); + } + if (nulls != null) { + request.put(KEY_NULL, nulls); + } + if (cast != null) { + request.put(KEY_CAST, cast); + } + if (combine != null) { + request.put(KEY_COMBINE, combine); + } + if (group != null) { + request.put(KEY_GROUP, group); + } + if (having != null) { + request.put(KEY_HAVING, having); + } + if (havingAnd != null) { + request.put(KEY_HAVING_AND, havingAnd); + } + if (order != null) { + request.put(KEY_ORDER, order); + } + if (keyMap != null) { + request.put(KEY_KEY, keyMap); + } + if (raw != null) { + request.put(KEY_RAW, raw); + } + if (json != null) { + request.put(KEY_JSON, json); + } + if (mthd != null) { + request.put(KEY_METHOD, mthd); + } } return config; @@ -4991,7 +5935,8 @@ 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); @@ -5007,15 +5952,17 @@ public static SQLConfig parseJoin(RequestMethod method, SQLCo 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); + 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(j.getCount()); 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() + " 不一致!"); + throw new IllegalArgumentException("主表 " + config.getTable() + " 的 @database:" + config.getDatabase() + + " 和它 SQL JOIN 的副表 " + table + " 的 @database:" + joinConfig.getDatabase() + " 不一致!"); } if (joinConfig.getSchema() == null) { joinConfig.setSchema(config.getSchema()); //主表 JOIN 副表,默认 schema 一致 @@ -5033,14 +5980,18 @@ else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { joinConfig.setMain(false).setKeyPrefix(true); 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); + SQLConfig outerConfig = newSQLConfig(method, table, alias, j.getOuter(), null, false, callback); + outerConfig.setMain(false) + .setKeyPrefix(true) + .setDatabase(joinConfig.getDatabase()) + .setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 + + j.setOuterConfig(outerConfig); } } - //解决 query: 1/2 查数量时报错 - /* SELECT count(*) AS count FROM sys.Moment AS Moment + //解决 query: 1/2 查数量时报错 + /* SELECT count(*) AS count FROM sys.Moment AS Moment LEFT JOIN ( SELECT count(*) AS count FROM sys.Comment ) AS Comment ON Comment.momentId = Moment.id LIMIT 1 OFFSET 0 */ if (RequestMethod.isHeadMethod(method, true)) { List onList = j.getOnList(); @@ -5099,7 +6050,7 @@ public static String getRealKey(RequestMethod method, String originKey return originKey; } - String key = new String(originKey); + String key = originKey; if (key.endsWith("$")) {//搜索 LIKE,查询时处理 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%' @@ -5111,13 +6062,15 @@ public static String getRealKey(RequestMethod method, String originKey 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 中有连续相同的占位符!"); + 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 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); + throw new IllegalArgumentException(originKey + ":value 中字符 " + originKey + + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); } } @@ -5134,16 +6087,16 @@ else if (key.endsWith("%")) {//数字、文本、日期范围 BETWEEN AND } else if (key.endsWith("{}")) {//被包含 IN,或者说key对应值处于value的范围内。查询时处理 key = key.substring(0, key.length() - 2); - } + } else if (key.endsWith("}{")) {//被包含 EXISTS,或者说key对应值处于value的范围内。查询时处理 key = key.substring(0, key.length() - 2); - } + } else if (key.endsWith("<>")) {//包含 json_contains,或者说value处于key对应值的范围内。查询时处理 key = key.substring(0, key.length() - 2); - } + } else if (key.endsWith("()")) {//方法,查询完后处理,先用一个Map保存 key = key.substring(0, key.length() - 2); - } + } else if (key.endsWith("@")) {//引用,引用对象查询完后处理。fillTarget中暂时不用处理,因为非GET请求都是由给定的id确定,不需要引用 key = key.substring(0, key.length() - 1); } @@ -5163,37 +6116,44 @@ 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); } } - //TODO if (key.endsWith("-")) { // 表示 key 和 value 顺序反过来: value LIKE key + // 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); - if ("&".equals(last) || "|".equals(last) || "!".equals(last)) { - key = key.substring(0, key.length() - 1); - } else { - last = null;//避免key + StringUtil.getString(last)错误延长 - } + // 不用Logic优化代码,否则 key 可能变为 key| 导致 key=value 变成 key|=value 而出错 + String 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) 错误延长 + } + + String len = ""; + if (key.endsWith("[") || key.endsWith("{")) { + len = key.substring(key.length() - 1); + key = key.substring(0, key.length() - 1); } - //"User:toUser":User转换"toUser":User, User为查询同名Table得到的JSONObject。交给客户端处理更好 - if (isTableKey) {//不允许在column key中使用Type:key形式 - key = Pair.parseEntry(key, true).getKey();//table以左边为准 + // "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以右边为准 + 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-' !"); + + " key:value 中的 key 只能关键词 '@key' 或 'key[长度符][逻辑符][条件符]' 或 PUT 请求下的 'key+' / 'key-' !" + + "长度符 只能为 [ - length 和 { - json_length,逻辑符 只能是 & - 与、| - 或、! - 非 !"); } + key += len; + if (saveLogic && last != null) { key = key + last; } @@ -5238,14 +6198,14 @@ public static interface Callback extends IdCallback { * @param table * @return */ - SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String datasource, 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 * @param key * @param request */ - public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception; + void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception; } public static Long LAST_ID; @@ -5263,7 +6223,7 @@ public T newId(RequestMethod method, String database, String schema, String data id = LAST_ID + 1; // 解决高并发下 id 冲突导致新增记录失败 } LAST_ID = id; - + return (T) id; } @@ -5279,9 +6239,56 @@ public String getUserIdKey(String database, String schema, String datasource, St @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 (ALLOW_MISSING_KEY_4_COMBINE) { + return; + } + throw new IllegalArgumentException(name + ":{} 里的 @combine:value 中的value里 " + + item + " 对应的条件 " + key + ":value 中 value 必须存在且不能为 null!"); + } + + } + + private static boolean isKeyInCombineExpr(String combineExpr, String key) { + while (combineExpr.isEmpty() == false) { + int index = combineExpr.indexOf(key); + if (index < 0) { + return false; + } + + 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 == ')' || right == ':')) { + return true; + } + int newIndex = index + key.length() + 1; + if (combineExpr.length() <= newIndex) { + break; + } + combineExpr = combineExpr.substring(newIndex); + } + + return false; + } + + + public List getWithAsExprSqlList() { + return withAsExprSqlList; + } + private void clearWithAsExprListIfNeed() { + // mysql8版本以上,子查询支持with as表达式 + if(this.isMySQL() && this.getDBVersionNums()[0] >= 8) { + this.withAsExprSqlList = new ArrayList<>(); } + } + @Override + public List getWithAsExprPreparedValueList() { + return this.withAsExprPreparedValueList; } + @Override + public AbstractSQLConfig setWithAsExprPreparedValueList(List list) { + this.withAsExprPreparedValueList = list; + return this; + } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 1f1d4bd96..9393f43be 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -5,49 +5,45 @@ package apijson.orm; +import apijson.*; +import apijson.orm.Join.On; +import apijson.orm.exception.NotExistException; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; + import java.io.BufferedReader; -import java.sql.Blob; -import java.sql.Clob; -import java.sql.Connection; -import java.sql.Date; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; -import java.sql.Savepoint; -import java.sql.Statement; -import java.sql.Time; -import java.sql.Timestamp; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.*; +import java.time.DayOfWeek; import java.time.LocalDateTime; -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; +import java.time.Month; +import java.time.Year; +import java.util.Date; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; import java.util.regex.Pattern; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; - -import apijson.JSONResponse; -import apijson.Log; -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 */ -public abstract class AbstractSQLExecutor implements SQLExecutor { +public abstract class AbstractSQLExecutor implements SQLExecutor { private static final String TAG = "AbstractSQLExecutor"; - + //是否返回 值为null的字段 + public static boolean ENABLE_OUTPUT_NULL_COLUMN = false; public static String KEY_RAW_LIST = "@RAW@LIST"; // 避免和字段命名冲突,不用 $RAW@LIST$ 是因为 $ 会在 fastjson 内部转义,浪费性能 + public static String KEY_VICE_ITEM = "@VICE@ITEM"; // 避免和字段命名冲突,不用 $VICE@LIST$ 是因为 $ 会在 fastjson 内部转义,浪费性能 + + private Parser parser; + @Override + public Parser getParser() { + return parser; + } + @Override + public AbstractSQLExecutor setParser(Parser parser) { + this.parser = parser; + return this; + } private int generatedSQLCount = 0; private int cachedSQLCount = 0; @@ -65,14 +61,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; @@ -87,36 +83,40 @@ public long getSqlResultDuration() { /**保存缓存 * @param sql key * @param list value - * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null */ @Override - public void putCache(String sql, List list, SQLConfig config) { + 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 key - * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null */ @Override - public List getCache(String sql, SQLConfig config) { + public List getCache(String sql, SQLConfig config) { return cacheMap.get(sql); } /**获取缓存 * @param sql key * @param position - * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null * @return */ @Override - public JSONObject getCacheItem(String sql, int position, SQLConfig config) { + public JSONObject getCacheItem(String sql, int position, SQLConfig config) { List list = getCache(sql, config); + return getCacheItem(list, position, config); + } + + public JSONObject getCacheItem(List list, int position, SQLConfig config) { // 只要 list 不为 null,则如果 list.get(position) == null,则返回 {} ,避免再次 SQL 查询 if (list == null) { return null; @@ -126,12 +126,16 @@ public JSONObject getCacheItem(String sql, int position, SQLConfig config) { return result != null ? result : new JSONObject(); } + + + + /**移除缓存 * @param sql key * @param config */ @Override - public void removeCache(String sql, SQLConfig config) { + public void removeCache(String sql, SQLConfig config) { if (sql == null) { Log.i(TAG, "removeList sql == null >> return;"); return; @@ -163,15 +167,10 @@ public ResultSet execute(@NotNull Statement statement, String sql) throws Except * @throws Exception */ @Override - public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Exception { + public JSONObject execute(@NotNull SQLConfig config, boolean unknownType) throws Exception { long executedSQLStartTime = System.currentTimeMillis(); - - boolean isPrepared = config.isPrepared(); - final String sql = config.getSQL(false); - config.setPrepared(isPrepared); - if (StringUtil.isEmpty(sql, true)) { Log.e(TAG, "execute StringUtil.isEmpty(sql, true) >> return null;"); return null; @@ -201,7 +200,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Map childMap = null; try { - if (unknowType) { + if (unknownType) { if (isExplain == false) { //只有 SELECT 才能 EXPLAIN executedSQLCount ++; executedSQLStartTime = System.currentTimeMillis(); @@ -212,14 +211,15 @@ 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); //导致后面 rs.getMetaData() 报错 Operation not allowed after ResultSet closed result.put("moreResults", statement.getMoreResults()); } else { - switch (config.getMethod()) { + RequestMethod method = config.getMethod(); + switch (method) { case POST: case PUT: case DELETE: @@ -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,18 +249,30 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws if (config.getIdIn() != null) { result.put(idKey + "[]", config.getIdIn()); } - + + if (method == RequestMethod.PUT || method == RequestMethod.DELETE) { + config.setMethod(RequestMethod.GET); + removeCache(config.getSQL(false), config); + config.setMethod(method); + } + return result; case GET: case GETS: case HEAD: case HEADS: - result = isHead || isExplain ? null : getCacheItem(sql, position, config); + List cache = getCache(sql, config); + result = getCacheItem(cache, position, config); Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result); if (result != null) { - cachedSQLCount ++; + if (isExplain == false) { + cachedSQLCount ++; + } + if (cache != null && cache.size() > 1) { + result.put(KEY_RAW_LIST, cache); + } Log.d(TAG, "\n\n execute result != null >> return result;" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); return result; } @@ -269,13 +281,13 @@ 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; } break; - default://OPTIONS, TRACE等 + default: //OPTIONS, TRACE等 Log.e(TAG, "execute sql = " + sql + " ; method = " + config.getMethod() + " >> return null;"); return null; } @@ -288,7 +300,12 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } result = AbstractParser.newSuccessResult(); - result.put(JSONResponse.KEY_COUNT, rs.getLong(1)); + // 兼容nosql,比如 elasticSearch-sql + if(config.isElasticsearch()) { + result.put(JSONResponse.KEY_COUNT, rs.getObject(1)); + }else { + result.put(JSONResponse.KEY_COUNT, rs.getLong(1)); + } resultList = new ArrayList<>(1); resultList.add(result); } @@ -316,23 +333,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(); @@ -348,7 +365,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } } } - + resultList = new ArrayList<>(capacity); } @@ -361,7 +378,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws //