diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md deleted file mode 100755 index 3683b0f3ea703828824b724c08004fe83419adfb..0000000000000000000000000000000000000000 --- 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 5a47bb25ed0954b96926c5b3fec04469566777eb..0000000000000000000000000000000000000000 --- 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 f8c0c4c7d9740cd4d1530df8ed6835f8041be85e..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..f0d9515f5d387c38deeb56e36118000e63d9f008 --- /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 0000000000000000000000000000000000000000..3ba13e0cec6cbbfd462e9ebf529dd2093148cd69 --- /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 0000000000000000000000000000000000000000..6f14d47b3b58585b9cc717a0f7ec4ad86c374256 --- /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 0000000000000000000000000000000000000000..9f9a6d0ea3798178d7ed4f9436b66d2908edd256 --- /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 204504dc57188d56e9ca457e3fa399f47af2ef01..a06bbd0a8eb43b1412ecccee7e78e671464f1ade 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 7e0850527c2408562dad75c4f433976f47f8456b..0cb431e27cda1df64b5914b396b7c8325f9d342f 100644 --- a/APIJSONORM/README.md +++ b/APIJSONORM/README.md @@ -1,4 +1,4 @@ -# APIJSONORM [![](https://jitpack.io/v/Tencent/APIJSON.svg)](https://jitpack.io/#Tencent/APIJSON) +# APIJSONORM [![](https://jitpack.io/v/Tencent/APIJSON.svg)](https://jitpack.io/#Tencent/APIJSON) [Ask DeepWiki.com](https://deepwiki.com/Tencent/APIJSON) 腾讯 [APIJSON](https://github.com/Tencent/APIJSON) ORM 库,可通过 Maven, Gradle 等远程依赖。
Tencent [APIJSON](https://github.com/Tencent/APIJSON) ORM library for remote dependencies with Maven, Gradle, etc. @@ -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 3f776fb6a776b764aedb2895f40a0a0b1c96faba..bd340fa5f48441a7519281dde2ddf3706ffc434c --- 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 + 8.0.2 jar APIJSONORM @@ -15,19 +15,12 @@ UTF-8 UTF-8 1.8 + UTF-8 + 1.8 + 1.8 - - com.alibaba - fastjson - 2.0.4 - - - javax.activation - activation - 1.1.1 - @@ -35,11 +28,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 b610cfc5b9139c2b75fedc7e19e1a911dc67e5ef..0a14e04202772c0d04179be244aa44ef5ea0e4b8 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -1,262 +1,691 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ package apijson; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.serializer.SerializerFeature; -import com.alibaba.fastjson2.JSONReader; - +import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; -/**阿里FastJSON封装类 防止解析时异常 +/**JSON工具类 防止解析时异常 * @author Lemon */ public class JSON { - private static final String TAG = "JSON"; - /**判断json格式是否正确 - * @param s - * @return - */ - public static boolean isJsonCorrect(String s) { - //太长 Log.i(TAG, "isJsonCorrect <<<< " + s + " >>>>>>>"); - if (s == null - // || s.equals("[]") - // || s.equals("{}") - || s.equals("") - || s.equals("[null]") - || s.equals("{null}") - || s.equals("null")) { - return false; - } - return true; + static final String TAG = "JSON"; + + public static JSONParser, ? extends List> DEFAULT_JSON_PARSER; + + static { + //DEFAULT_JSON_PARSER = new JSONParser, List>() { + // + // @Override + // public LinkedHashMap createJSONObject() { + // throw new UnsupportedOperationException(); + // } + // + // @Override + // public List createJSONArray() { + // throw new UnsupportedOperationException(); + // } + // + // @Override + // public String toJSONString(Object obj, boolean format) { + // throw new UnsupportedOperationException(); + // } + // + // @Override + // public Object parse(Object json) { + // throw new UnsupportedOperationException(); + // } + // + // @Override + // public LinkedHashMap parseObject(Object json) { + // throw new UnsupportedOperationException(); + // } + // + // @Override + // public T parseObject(Object json, Class clazz) { + // throw new UnsupportedOperationException(); + // } + // + // @Override + // public List parseArray(Object json) { + // throw new UnsupportedOperationException(); + // } + // + // @Override + // public List parseArray(Object json, Class clazz) { + // throw new UnsupportedOperationException(); + // } + // + //}; + } - /**获取有效的json - * @param s - * @return - */ - public static String getCorrectJson(String s) { - return getCorrectJson(s, false); +// public static JSONCreator, ? extends List> DEFAULT_JSON_CREATOR = DEFAULT_JSON_PARSER; +// public static > M newObj() { +// return createJSONObject(); +// } +// public static > M newObj(String key, Object value) { +// return createJSONObject(key, value); +// } +// public static > M newObj(Map map) { +// return createJSONObject(map); +// } + + public static > M createJSONObject() { + return (M) DEFAULT_JSON_PARSER.createJSONObject(); } - /**获取有效的json - * @param s - * @param isArray - * @return - */ - public static String getCorrectJson(String s, boolean isArray) { - s = StringUtil.getTrimedString(s); - // if (isArray) { - // while (s.startsWith("\"")) { - // s = s.substring(1); - // } - // while (s.endsWith("\"")) { - // s = s.substring(0, s.length() - 1); - // } - // } - return s;//isJsonCorrect(s) ? s : null; + public static > M createJSONObject(String key, Object value) { + return (M) DEFAULT_JSON_PARSER.createJSONObject(key, value); + } + public static > M createJSONObject(Map map) { + return (M) DEFAULT_JSON_PARSER.createJSONObject(map); + } + + //public static > L newArr() { + // return createJSONArray(); + //} + //public static > L newArr(Object obj) { + // return createJSONArray(obj); + //} + //public static > L newArr(List list) { + // return createJSONArray(list); + //} + + public static > L createJSONArray() { + return (L) DEFAULT_JSON_PARSER.createJSONArray(); + } + public static > L createJSONArray(Object obj) { + return (L) DEFAULT_JSON_PARSER.createJSONArray(obj); + } + public static > L createJSONArray(Collection list) { + return (L) DEFAULT_JSON_PARSER.createJSONArray(list); + } + + public static Object parse(Object json) { + return DEFAULT_JSON_PARSER.parse(json); + } + + + public static > M parseObject(Object json) { + String s = toJSONString(json); + if (StringUtil.isEmpty(s, true)) { + return null; + } + + return (M) DEFAULT_JSON_PARSER.parseObject(s); + } + + public static T parseObject(Object json, Class clazz) { + String s = toJSONString(json); + if (StringUtil.isEmpty(s, true)) { + return null; + } + + return DEFAULT_JSON_PARSER.parseObject(s, clazz); } /** * @param json * @return */ - private static final JSONReader.Feature[] DEFAULT_FASTJSON_FEATURES = {JSONReader.Feature.FieldBased, JSONReader.Feature.UseBigDecimalForDoubles}; - public static Object parse(Object obj) { + public static > L parseArray(Object json) { + String s = toJSONString(json); + if (StringUtil.isEmpty(s, true)) { + return null; + } + try { - return com.alibaba.fastjson2.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); + L arr = (L) DEFAULT_JSON_PARSER.parseArray(s); + return arr; } catch (Exception e) { - Log.i(TAG, "parse catch \n" + e.getMessage()); + Log.i(TAG, "parseArray catch \n" + e.getMessage()); } return null; } - /**obj转JSONObject - * @param obj - * @return - */ - public static JSONObject parseObject(Object obj) { - if (obj instanceof JSONObject) { - return (JSONObject) obj; + public static List parseArray(Object json, Class clazz) { + String s = toJSONString(json); + if (StringUtil.isEmpty(s, true)) { + return null; } - return parseObject(toJSONString(obj)); + + try { + return DEFAULT_JSON_PARSER.parseArray(s, clazz); + } catch (Exception e) { + Log.i(TAG, "parseArray catch \n" + e.getMessage()); + } + return null; } - /**json转JSONObject - * @param json + + /** + * @param obj * @return */ - public static JSONObject parseObject(String json) { - return parseObject(json, JSONObject.class); + public static String format(Object obj) { + return toJSONString(obj, true); } - /**json转实体类 - * @param json - * @param clazz + /** + * @param obj * @return */ - public static T parseObject(String json, Class clazz) { - if (clazz == null) { - Log.e(TAG, "parseObject clazz == null >> return null;"); - } else { - try { - return com.alibaba.fastjson2.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); - } catch (Exception e) { - Log.i(TAG, "parseObject catch \n" + e.getMessage()); - } + public static String toJSONString(Object obj) { + return toJSONString(obj, false); + } + public static String toJSONString(Object obj, boolean format) { + if (obj == null) { + return null; } - return null; + + if (obj instanceof String) { + return (String) obj; + } + + //if (obj instanceof Map) { + // // Simple JSON object format + // StringBuilder sb = new StringBuilder("{"); + // @SuppressWarnings("unchecked") + // Map map = (Map) obj; + // boolean first = true; + // for (Map.Entry entry : map.entrySet()) { + // if (! first) { + // sb.append(","); + // } + // + // first = false; + // sb.append("\"").append(entry.getKey()).append("\":"); + // Object value = entry.getValue(); + // if (value instanceof String) { + // sb.append("\"").append(value).append("\""); + // } else { + // sb.append(toJSONString(value)); + // } + // } + // sb.append("}"); + // return sb.toString(); + //} + // + //if (obj instanceof List) { + // StringBuilder sb = new StringBuilder("["); + // @SuppressWarnings("unchecked") + // List list = (List) obj; + // boolean first = true; + // for (Object item : list) { + // if (! first) { + // sb.append(","); + // } + // first = false; + // if (item instanceof String) { + // sb.append("\"").append(item).append("\""); + // } else { + // sb.append(toJSONString(item)); + // } + // } + // sb.append("]"); + // return sb.toString(); + //} + + return DEFAULT_JSON_PARSER.toJSONString(obj, format); } - /**list转JSONArray - * @param list + + /**判断是否为JSONObject或JSONArray的isXxx方法名 + * @param key * @return */ - public static JSONArray parseArray(List list) { - return new JSONArray(list); + public static boolean isJSONType(String key) { + return key != null && key.startsWith("is") && key.length() > 2 && key.contains("JSON"); } - /**obj转JSONArray - * @param obj - * @return + + public static boolean isBoolOrNumOrStr(Object obj) { + return obj instanceof Boolean || obj instanceof Number || obj instanceof String; + } + + /** + * Get a value from a Map and convert to the specified type + * @param map Source map + * @param key The key + * @param Target type + * @return The converted value + */ + @SuppressWarnings("unchecked") + public static T get(Map map, String key) { + return map == null || key == null ? null : (T) map.get(key); + } + + /** + * Get a value from a Map and convert to the specified type + * @param map Source map + * @param key The key + * @param Target type + * @return The converted value + */ + @SuppressWarnings("unchecked") + public static > M getJSONObject(Map map, String key) { + Object obj = get(map, key); + return (M) obj; + } + + /** + * Get a value from a Map and convert to the specified type + * @param map Source map + * @param key The key + * @param Target type + * @return The converted value */ - public static JSONArray parseArray(Object obj) { - if (obj instanceof JSONArray) { - return (JSONArray) obj; + @SuppressWarnings("unchecked") + public static > L getJSONArray(Map map, String key) { + Object obj = get(map, key); + return (L) obj; + } + + /** + * Get a value from a Map and convert to the specified type + * @param list Source map + * @param index The key + * @param Target type + * @return The converted value + */ + @SuppressWarnings("unchecked") + public static T get(List list, int index) { + return list == null || index < 0 || index >= list.size() ? null : (T) list.get(index); + } + + @SuppressWarnings("unchecked") + public static > M getJSONObject(List list, int index) { + Object obj = get(list, index); + return (M) obj; + } + + @SuppressWarnings("unchecked") + public static > L getJSONArray(List list, int index) { + Object obj = get(list, index); + return (L) obj; + } + +// /** +// * Get a value from a Map and convert to the specified type +// * @param map Source map +// * @param key The key +// * @param Target type +// * @return The converted value +// */ +// @SuppressWarnings("unchecked") +// public static T get(List list, int index) { +// return list == null || index < 0 || index >= list.size() ? null : list.get(index); +// } + + /** + * Get a Map value from a Map + * @param map Source map + * @param key The key + * @return The Map value + * @throws IllegalArgumentException If value is not a Map and cannot be converted + */ + @SuppressWarnings("unchecked") + public static Map getMap(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; + } + + if (value instanceof Map) { + return (Map) value; } - return parseArray(toJSONString(obj)); + + throw new IllegalArgumentException("Value for key '" + key + "' is not a Map: " + value.getClass().getName()); } - /**json转JSONArray - * @param json - * @return + + /** + * Get a List value from a Map + * @param map Source map + * @param key The key + * @return The List value + * @throws IllegalArgumentException If value is not a List and cannot be converted */ - 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()); + @SuppressWarnings("unchecked") + public static List getList(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; } - return null; + + if (value instanceof List) { + return (List) value; + } + + throw new IllegalArgumentException("Value for key '" + key + "' is not a List: " + value.getClass().getName()); } - /**JSONArray转实体类列表 - * @param array - * @param clazz - * @return + + /** + * Get an int value from a Map + * @param map Source map + * @param key The key + * @return The int value + * @throws IllegalArgumentException If value cannot be converted to int */ - public static List parseArray(JSONArray array, Class clazz) { - return parseArray(toJSONString(array), clazz); + public static Integer getInteger(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; + } + + if (value instanceof Number) { + return ((Number) value).intValue(); + } + + if (value instanceof String) { + try { + return Integer.parseInt((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to int: " + e.getMessage()); + } + } + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to int"); } - /**json转实体类列表 - * @param json - * @param clazz - * @return + + /** + * Get an int value from a Map + * @param map Source map + * @param key The key + * @return The int value + * @throws IllegalArgumentException If value cannot be converted to int */ - public static List parseArray(String json, Class clazz) { - if (clazz == null) { - Log.e(TAG, "parseArray clazz == null >> return null;"); - } else { + public static int getIntValue(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return 0; + } + + if (value instanceof Number) { + return ((Number) value).intValue(); + } + + if (value instanceof String) { try { - return com.alibaba.fastjson.JSON.parseArray(getCorrectJson(json, true), clazz); - } catch (Exception e) { - Log.i(TAG, "parseArray catch \n" + e.getMessage()); + return Integer.parseInt((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to int: " + e.getMessage()); } } - return null; + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to int"); } - /**实体类转json - * @param obj - * @return + /** + * Get an int value from a Map + * @param map Source map + * @param key The key + * @return The int value + * @throws IllegalArgumentException If value cannot be converted to int */ - public static String toJSONString(Object obj) { - if (obj instanceof String) { - return (String) obj; + public static Long getLong(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; } - try { - return com.alibaba.fastjson.JSON.toJSONString(obj); - } catch (Exception e) { - Log.e(TAG, "toJSONString catch \n" + e.getMessage()); + + if (value instanceof Number) { + return ((Number) value).longValue(); } - return null; + + if (value instanceof String) { + try { + return Long.parseLong((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to int: " + e.getMessage()); + } + } + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to int"); } - /**实体类转json - * @param obj - * @param features - * @return + /** + * Get a long value from a Map + * @param map Source map + * @param key The key + * @return The long value + * @throws IllegalArgumentException If value cannot be converted to long */ - public static String toJSONString(Object obj, SerializerFeature... features) { - if (obj instanceof String) { - return (String) obj; + public static long getLongValue(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return 0; } - try { - return com.alibaba.fastjson.JSON.toJSONString(obj, features); - } catch (Exception e) { - Log.e(TAG, "parseArray catch \n" + e.getMessage()); + + if (value instanceof Number) { + return ((Number) value).longValue(); } - return null; + + if (value instanceof String) { + try { + return Long.parseLong((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to long: " + e.getMessage()); + } + } + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to long"); } - /**格式化,显示更好看 - * @param json - * @return + /** + * Get a double value from a Map + * @param map Source map + * @param key The key + * @return The double value + * @throws IllegalArgumentException If value cannot be converted to double */ - public static String format(String json) { - return format(parse(json)); + public static Float getFloat(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; + } + + if (value instanceof Number) { + return ((Number) value).floatValue(); + } + + if (value instanceof String) { + try { + return Float.parseFloat((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to double: " + e.getMessage()); + } + } + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to double"); } - /**格式化,显示更好看 - * @param object - * @return + + /** + * Get a double value from a Map + * @param map Source map + * @param key The key + * @return The double value + * @throws IllegalArgumentException If value cannot be converted to double */ - public static String format(Object object) { - return toJSONString(object, SerializerFeature.PrettyFormat); + public static float getFloatValue(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return 0; + } + + if (value instanceof Number) { + return ((Number) value).floatValue(); + } + + if (value instanceof String) { + try { + return Float.parseFloat((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to double: " + e.getMessage()); + } + } + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to double"); } - /**判断是否为JSONObject - * @param obj instanceof String ? parseObject - * @return + + /** + * Get a double value from a Map + * @param map Source map + * @param key The key + * @return The double value + * @throws IllegalArgumentException If value cannot be converted to double */ - public static boolean isJSONObject(Object obj) { - if (obj instanceof JSONObject) { - return true; + public static Double getDouble(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; } - if (obj instanceof String) { + + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + + if (value instanceof String) { try { - JSONObject json = parseObject((String) obj); - return json != null && json.isEmpty() == false; - } catch (Exception e) { - Log.e(TAG, "isJSONObject catch \n" + e.getMessage()); + return Double.parseDouble((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to double: " + e.getMessage()); } } - return false; + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to double"); } - /**判断是否为JSONArray - * @param obj instanceof String ? parseArray - * @return + + /** + * Get a double value from a Map + * @param map Source map + * @param key The key + * @return The double value + * @throws IllegalArgumentException If value cannot be converted to double */ - public static boolean isJSONArray(Object obj) { - if (obj instanceof JSONArray) { - return true; + public static double getDoubleValue(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return 0; } - if (obj instanceof String) { + + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + + if (value instanceof String) { try { - JSONArray json = parseArray((String) obj); - return json != null && json.isEmpty() == false; - } catch (Exception e) { - Log.e(TAG, "isJSONArray catch \n" + e.getMessage()); + return Double.parseDouble((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to double: " + e.getMessage()); } } - return false; + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to double"); } - /**判断是否为 Boolean,Number,String 中的一种 - * @param obj - * @return + + /** + * Get a boolean value from a Map + * @param map Source map + * @param key The key + * @return The boolean value + * @throws IllegalArgumentException If value cannot be converted to boolean */ - public static boolean isBooleanOrNumberOrString(Object obj) { - return obj instanceof Boolean || obj instanceof Number || obj instanceof String; + public static Boolean getBoolean(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; + } + + if (value instanceof Boolean) { + return (Boolean) value; + } + + if (value instanceof String) { + String str = ((String) value).toLowerCase(); + if (str.equals("true") || str.equals("false")) { + return Boolean.parseBoolean(str); + } + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to boolean"); + } + + if (value instanceof Number) { + int intValue = ((Number) value).intValue(); + if (intValue == 0 || intValue == 1) { + return intValue != 0; + } + throw new IllegalArgumentException("Cannot convert Number value '" + value + "' to boolean. Only 0 and 1 are supported."); + } + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to boolean"); + } + + /** + * Get a boolean value from a Map + * @param map Source map + * @param key The key + * @return The boolean value + * @throws IllegalArgumentException If value cannot be converted to boolean + */ + public static boolean getBooleanValue(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return false; + } + + if (value instanceof Boolean) { + return (Boolean) value; + } + + if (value instanceof String) { + String str = ((String) value).toLowerCase(); + if (str.equals("true") || str.equals("false")) { + return Boolean.parseBoolean(str); + } + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to boolean"); + } + + if (value instanceof Number) { + int intValue = ((Number) value).intValue(); + if (intValue == 0 || intValue == 1) { + return intValue != 0; + } + throw new IllegalArgumentException("Cannot convert Number value '" + value + "' to boolean. Only 0 and 1 are supported."); + } + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to boolean"); + } + + /** + * Get a string value from a Map + * @param map Source map + * @param key The key + * @return The string value + */ + public static String getString(Map map, String key) { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; + } + + return value.toString(); + } + + + public static Object getFromObjOrArr(Object parent, String k) { + if (parent instanceof Map) { + return ((Map) parent).get(k); + } + + if (parent instanceof List) { + int j = Integer.valueOf(k); + return ((List) parent).get(j); + } + + return null; } } diff --git a/APIJSONORM/src/main/java/apijson/JSONCreator.java b/APIJSONORM/src/main/java/apijson/JSONCreator.java new file mode 100755 index 0000000000000000000000000000000000000000..df0d9066a05285d104ed4bc821dccdef2b38e106 --- /dev/null +++ b/APIJSONORM/src/main/java/apijson/JSONCreator.java @@ -0,0 +1,54 @@ +/*Copyright (C) 2020 Tencent. All rights reserved. + +This source code is licensed under the Apache License Version 2.0.*/ + + +package apijson; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/**JSON相关创建器 + * @author Lemon + */ +public interface JSONCreator, L extends List> { + + @NotNull + M createJSONObject(); + + @NotNull + default M createJSONObject(String key, Object value) { + M obj = createJSONObject(); + obj.put(key, value); + return obj; + } + + @NotNull + default M createJSONObject(Map map) { + M obj = createJSONObject(); + if (map != null && ! map.isEmpty()) { + obj.putAll(map); + } + return obj; + } + + @NotNull + L createJSONArray(); + + @NotNull + default L createJSONArray(Object obj){ + L arr = createJSONArray(); + arr.add(obj); + return arr; + } + + @NotNull + default L createJSONArray(Collection list){ + L arr = createJSONArray(); + if (list != null && ! list.isEmpty()) { + arr.addAll(list); + } + return arr; + } +} diff --git a/APIJSONORM/src/main/java/apijson/JSONList.java b/APIJSONORM/src/main/java/apijson/JSONList.java new file mode 100644 index 0000000000000000000000000000000000000000..092bf9f3950d1ad9e27de32f66816c20074e8c1f --- /dev/null +++ b/APIJSONORM/src/main/java/apijson/JSONList.java @@ -0,0 +1,312 @@ +/*Copyright (C) 2020 Tencent. All rights reserved. + +This source code is licensed under the Apache License Version 2.0.*/ + +package apijson; + +import java.util.*; + +/** + * Custom JSONList implementation based on ArrayList to replace com.alibaba.fastjson.JSONList + * Maintains same API as fastjson but uses standard Java List implementation + * @author Lemon + */ +public interface JSONList, L extends List> extends List { + public static final String TAG = "JSONList"; + + ///** + // * Create an empty JSONList + // */ + //default JSONList() { + // super(); + //} + // + //private int initialCapacity = 10; + ///** + // * Create a JSONList with initial capacity + // * @param initialCapacity the initial capacity + // */ + //default JSONList(int initialCapacity) { + // super(initialCapacity); + //} + // + ///** + // * Create a JSONList from a Collection + // * @param collection the collection to copy from + // */ + //default JSONList(Collection collection) { + // super(collection); + //} + // + ///** + // * Create a JSONList from a JSON string + // * @param json JSON string + // */ + //default JSONList(String json) { + // this(); + // List list = JSON.parseArray(json); + // if (list != null) { + // addAll(list); + // } + //} + // + /** + * Get a JSONMap at the specified index + * @param index the index + * @return the JSONMap or null if not a JSONMap + */ + default M getJSONObject(int index) { + if (index < 0 || index >= size()) { + return null; + } + + Object obj = get(index); + if (obj instanceof Map) { + return JSON.createJSONObject((Map) obj); + } + + return null; + } + + /** + * Get a JSONList at the specified index + * @param index the index + * @return the JSONList or null if not a JSONList + */ + default L getJSONArray(int index) { + if (index < 0 || index >= size()) { + return null; + } + + Object obj = get(index); + if (obj instanceof List) { + return JSON.createJSONArray((List) obj); + } + + return null; + } + + /** + * Get a boolean value at the specified index + * @param index the index + * @return the boolean value or false if not found + */ + default boolean getBooleanValue(int index) { + if (index < 0 || index >= size()) { + return false; + } + + Object obj = get(index); + if (obj instanceof Boolean) { + return (Boolean) obj; + } else if (obj instanceof Number) { + return ((Number) obj).intValue() != 0; + } else if (obj instanceof String) { + return Boolean.parseBoolean((String) obj); + } + + return false; + } + + /** + * Get an integer value at the specified index + * @param index the index + * @return the integer value or 0 if not found + */ + default int getIntValue(int index) { + if (index < 0 || index >= size()) { + return 0; + } + + Object obj = get(index); + if (obj instanceof Number) { + return ((Number) obj).intValue(); + } else if (obj instanceof String) { + try { + return Integer.parseInt((String) obj); + } catch (NumberFormatException e) { + // Ignore + } + } + return 0; + } + + /** + * Get a long value at the specified index + * @param index the index + * @return the long value or 0 if not found + */ + default long getLongValue(int index) { + if (index < 0 || index >= size()) { + return 0L; + } + + Object obj = get(index); + if (obj instanceof Number) { + return ((Number) obj).longValue(); + } else if (obj instanceof String) { + try { + return Long.parseLong((String) obj); + } catch (NumberFormatException e) { + // Ignore + } + } + return 0L; + } + + /** + * Get a double value at the specified index + * @param index the index + * @return the double value or 0 if not found + */ + default double getDoubleValue(int index) { + if (index < 0 || index >= size()) { + return 0.0; + } + + Object obj = get(index); + if (obj instanceof Number) { + return ((Number) obj).doubleValue(); + } else if (obj instanceof String) { + try { + return Double.parseDouble((String) obj); + } catch (NumberFormatException e) { + // Ignore + } + } + return 0.0; + } + + /** + * Get a string value at the specified index + * @param index the index + * @return the string value or null if not found + */ + default String getString(int index) { + if (index < 0 || index >= size()) { + return null; + } + + Object obj = get(index); + return obj != null ? obj.toString() : null; + } + + + default String toJSONString() { + return JSON.toJSONString(this); + } + + //@Override + //default boolean containsAll(Collection c) { + // if (c == null || c.isEmpty()) { + // return true; + // } + // return super.containsAll(c); + //} + // + //@Override + //default boolean addAll(Collection c) { + // if (c == null || c.isEmpty()) { + // return true; + // } + // return super.addAll(c); + //} + // + //@Override + //default boolean addAll(int index, Collection c) { + // if (c == null || c.isEmpty()) { + // return true; + // } + // + // int sz = size(); + // if (index < 0 || index >= sz) { + // index += sz; + // } + // + // return super.addAll(index, c); + //} + // + //@Override + //default boolean removeAll(Collection c) { + // if (c == null || c.isEmpty()) { + // return true; + // } + // return super.removeAll(c); + //} + // + //@Override + //default boolean retainAll(Collection c) { + // if (c == null || c.isEmpty()) { + // return true; + // } + // return super.retainAll(c); + //} + // + // + //@Override + //default Object get(int index) { + // int sz = size(); + // if (index < 0 || index >= sz) { + // index += sz; + // } + // + // return super.get(index); + //} + // + //@Override + //default Object set(int index, Object element) { + // int sz = size(); + // if (index < 0 || index >= sz) { + // index += sz; + // } + // + // return super.set(index, element); + //} + // + //@Override + //default void add(int index, Object element) { + // int sz = size(); + // if (index < 0 || index >= sz) { + // index += sz; + // } + // + // super.add(index, element); + //} + // + //@Override + //default Object remove(int index) { + // int sz = size(); + // if (index < 0 && index >= -sz) { + // index += sz; + // } + // if (index < 0 || index >= sz) { + // return null; + // } + // + // return super.remove(index); + //} + // + //@Override + //default ListIterator listIterator(int index) { + // int sz = size(); + // if (index < 0 && index >= -sz) { + // index += sz; + // } + // + // return super.listIterator(index); + //} + // + //@Override + //default List subList(int fromIndex, int toIndex) { + // int sz = size(); + // if (fromIndex < 0 && fromIndex >= -sz) { + // fromIndex += sz; + // } + // if (toIndex < 0 && toIndex >= -sz) { + // toIndex += sz; + // } + // + // return super.subList(fromIndex, toIndex); + //} + +} \ No newline at end of file diff --git a/APIJSONORM/src/main/java/apijson/JSONObject.java b/APIJSONORM/src/main/java/apijson/JSONMap.java similarity index 33% rename from APIJSONORM/src/main/java/apijson/JSONObject.java rename to APIJSONORM/src/main/java/apijson/JSONMap.java index 6825a02de43588a29d0e9f2ad633e5b195e5e9ac..29d88f756f068524a4bc740a0ae9717963f5fa3d 100755 --- a/APIJSONORM/src/main/java/apijson/JSONObject.java +++ b/APIJSONORM/src/main/java/apijson/JSONMap.java @@ -1,59 +1,81 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ package apijson; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; -/**use this class instead of com.alibaba.fastjson.JSONObject + +/**use this class instead of com.alibaba.fastjson.JSONMap * @author Lemon * @see #put * @see #puts * @see #putsAll */ -public class JSONObject extends com.alibaba.fastjson.JSONObject { - private static final long serialVersionUID = 1L; - - private static final String TAG = "JSONObject"; - - - /**ordered - */ - public JSONObject() { - super(true); - } - /**transfer Object to JSONObject - * @param object - * @see {@link #JSONObject(Object)} - */ - public JSONObject(Object object) { - this(toJSONString(object)); - } - /**parse JSONObject with JSON String - * @param json - * @see {@link #JSONObject(String)} - */ - public JSONObject(String json) { - this(parseObject(json)); - } - /**transfer com.alibaba.fastjson.JSONObject to JSONObject - * @param object - * @see {@link #putsAll(Map)} - */ - public JSONObject(com.alibaba.fastjson.JSONObject object) { - this(); - putsAll(object); - } - - - +//default class JSONMap extends LinkedHashMap { +public interface JSONMap, L extends List> extends Map { + static final String TAG = "JSONMap"; + + // 只能是 static public Map map = new LinkedHashMap<>(); + + ///**ordered + // */ + //default JSONMap() { + // super(); + //} + ///**transfer Object to JSONMap + // * @param object + // * @see {@link #JSONMap(Object)} + // */ + //default JSONMap(Object object) { + // this(); + // if (object instanceof Map) { + // @SuppressWarnings("unchecked") + // Map map = (Map) object; + // putAll(map); + // } else if (object != null) { + // String json = JSON.toJSONString(object); + // if (json != null) { + // Map map = JSON.parseObject(json); + // if (map != null) { + // putAll(map); + // } + // } + // } + //} + ///**parse JSONMap with JSON String + // * @param json + // * @see {@link #JSONMap(String)} + // */ + //default JSONMap(String json) { + // this(); + // Map map = JSON.parseObject(json); + // if (map != null) { + // putAll(map); + // } + //} + ///**transfer com.alibaba.fastjson.JSONMap to JSONMap + // * @param object + // * @see {@link #putsAll(Map)} + // */ + //default JSONMap(Map object) { + // this(); + // putsAll(object); + //} + + //public static JSONMap valueOf(Object obj) { + // JSONMap req = new JSONMap() {}; + // Map m = JSON.parseObject(obj); + // if (m != null && ! m.isEmpty()) { + // req.map.putAll(m); + // } + // return req; + //} //judge <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - public static final String KEY_ARRAY = "[]"; + String KEY_ARRAY = "[]"; /**判断是否为Array的key * @param key @@ -91,14 +113,14 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param id * @return */ - public JSONObject setId(Long id) { + default JSONMap setId(Long id) { return puts(KEY_ID, id); } /**set "id{}":[] in Table layer * @param list * @return */ - public JSONObject setIdIn(List list) { + default JSONMap setIdIn(List list) { return puts(KEY_ID_IN, list); } @@ -106,73 +128,101 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param id * @return */ - public JSONObject setUserId(Long id) { + default JSONMap setUserId(Long id) { return puts(KEY_USER_ID, id); } /**set "userId{}":[] in Table layer * @param list * @return */ - public JSONObject setUserIdIn(List list) { + default JSONMap setUserIdIn(List list) { return puts(KEY_USER_ID_IN, list); } - public static final int CACHE_ALL = 0; - public static final int CACHE_ROM = 1; - public static final int CACHE_RAM = 2; + int CACHE_ALL = 0; + int CACHE_ROM = 1; + int CACHE_RAM = 2; - public static final String CACHE_ALL_STRING = "ALL"; - public static final String CACHE_ROM_STRING = "ROM"; - public static final String CACHE_RAM_STRING = "RAM"; + String CACHE_ALL_STRING = "ALL"; + String CACHE_ROM_STRING = "ROM"; + String CACHE_RAM_STRING = "RAM"; //@key关键字都放这个类 <<<<<<<<<<<<<<<<<<<<<< - public static final String KEY_TRY = "@try"; //尝试,忽略异常 - public static final String KEY_CATCH = "@catch"; //TODO 捕捉到异常后,处理方式 null-不处理;DEFAULT-返回默认值;ORIGIN-返回请求里的原始值 - public static final String KEY_DROP = "@drop"; //丢弃,不返回,TODO 应该通过 fastjson 的 ignore 之类的机制来处理,避免导致下面的对象也不返回 - // public static final String KEY_KEEP = "@keep"; //一定会返回,为 null 或 空对象时,会使用默认值(非空),解决其它对象因为不关联的第一个对为空导致也不返回 - public static final String KEY_DEFULT = "@default"; //TODO 自定义默认值 { "@default":true },@default 可完全替代 @keep - public static final String KEY_NULL = "@null"; //TODO 值为 null 的键值对 "@null":"tag,pictureList",允许 is NULL 条件判断, SET tag = NULL 修改值为 NULL 等 - public static final String KEY_CAST = "@cast"; //TODO 类型转换 cast(date AS DATE) - - public static final String KEY_ROLE = "@role"; //角色,拥有对某些数据的某些操作的权限 - public static final String KEY_DATABASE = "@database"; //数据库类型,默认为MySQL - public static final String KEY_SCHEMA = "@schema"; //数据库,Table在非默认schema内时需要声明 - public static final String KEY_DATASOURCE = "@datasource"; //数据源 - public static final String KEY_EXPLAIN = "@explain"; //分析 true/false - public static final String KEY_CACHE = "@cache"; //缓存 RAM/ROM/ALL - public static final String KEY_COLUMN = "@column"; //查询的Table字段或SQL函数 - public static final String KEY_FROM = "@from"; //FROM语句 - public static final String KEY_COMBINE = "@combine"; //条件组合,每个条件key前面可以放&,|,!逻辑关系 "id!{},&sex,!name&$" - public static final String KEY_GROUP = "@group"; //分组方式 - public static final String KEY_HAVING = "@having"; //聚合函数条件,一般和@group一起用 - public static final String KEY_HAVING_AND = "@having&"; //聚合函数条件,一般和@group一起用 - public static final String KEY_ORDER = "@order"; //排序方式 - public static final String KEY_RAW = "@raw"; // 自定义原始 SQL 片段 - public static final String KEY_JSON = "@json"; //SQL Server 把字段转为 JSON 输出 - - 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_EXPLAIN); - TABLE_KEY_LIST.add(KEY_CACHE); - TABLE_KEY_LIST.add(KEY_COLUMN); - TABLE_KEY_LIST.add(KEY_FROM); - TABLE_KEY_LIST.add(KEY_NULL); - TABLE_KEY_LIST.add(KEY_CAST); - TABLE_KEY_LIST.add(KEY_COMBINE); - TABLE_KEY_LIST.add(KEY_GROUP); - TABLE_KEY_LIST.add(KEY_HAVING); - TABLE_KEY_LIST.add(KEY_HAVING_AND); - TABLE_KEY_LIST.add(KEY_ORDER); - TABLE_KEY_LIST.add(KEY_RAW); - TABLE_KEY_LIST.add(KEY_JSON); - } + String KEY_TRY = "@try"; //尝试,忽略异常 + String KEY_CATCH = "@catch"; //TODO 捕捉到异常后,处理方式 null-不处理;DEFAULT-返回默认值;ORIGIN-返回请求里的原始值 + String KEY_DROP = "@drop"; //丢弃,不返回,TODO 应该通过 fastjson 的 ignore 之类的机制来处理,避免导致下面的对象也不返回 + // String KEY_KEEP = "@keep"; //一定会返回,为 null 或 空对象时,会使用默认值(非空),解决其它对象因为不关联的第一个对为空导致也不返回 + String KEY_DEFULT = "@default"; //TODO 自定义默认值 { "@default":true },@default 可完全替代 @keep + String KEY_NULL = "@null"; //值为 null 的键值对 "@null":"tag,pictureList",允许 is NULL 条件判断, SET tag = NULL 修改值为 NULL 等 + String KEY_CAST = "@cast"; //类型转换 cast(date AS DATE) + + String KEY_ROLE = "@role"; //角色,拥有对某些数据的某些操作的权限 + String KEY_DATABASE = "@database"; //数据库类型,默认为MySQL + String KEY_DATASOURCE = "@datasource"; //数据源 + String KEY_NAMESPACE = "@namespace"; //命名空间,Table 在非默认 namespace 内时需要声明 + String KEY_CATALOG = "@catalog"; //目录,Table 在非默认 catalog 内时需要声明 + String KEY_SCHEMA = "@schema"; //数据库,Table 在非默认 schema 内时需要声明 + String KEY_EXPLAIN = "@explain"; //分析 true/false + String KEY_CACHE = "@cache"; //缓存 RAM/ROM/ALL + String KEY_COLUMN = "@column"; //查询的Table字段或SQL函数 + String KEY_FROM = "@from"; //FROM语句 + String KEY_COMBINE = "@combine"; //条件组合,每个条件key前面可以放&,|,!逻辑关系 "id!{},&sex,!name&$" + String KEY_GROUP = "@group"; //分组方式 + String KEY_HAVING = "@having"; //聚合函数条件,一般和@group一起用 + String KEY_HAVING_AND = "@having&"; //聚合函数条件,一般和@group一起用 + String KEY_SAMPLE = "@sample"; //取样方式 + String KEY_LATEST = "@latest"; //最近方式 + String KEY_PARTITION = "@partition"; //分区方式 + String KEY_FILL = "@fill"; //填充方式 + String KEY_ORDER = "@order"; //排序方式 + String KEY_KEY = "@key"; // key 映射,year:left(date,4);name_tag:(name,tag) + String KEY_RAW = "@raw"; // 自定义原始 SQL 片段 + String KEY_JSON = "@json"; //SQL Server 把字段转为 JSON 输出 + String KEY_METHOD = "@method"; // json 对象配置操作方法 + String KEY_GET = "@get"; // json 对象配置操作方法 + String KEY_GETS = "@gets"; // json 对象配置操作方法 + String KEY_HEAD = "@head"; // json 对象配置操作方法 + String KEY_HEADS = "@heads"; // json 对象配置操作方法 + String KEY_POST = "@post"; // json 对象配置操作方法 + String KEY_PUT = "@put"; // json 对象配置操作方法 + String KEY_DELETE = "@delete"; // json 对象配置操作方法 + + List TABLE_KEY_LIST = new ArrayList<>(Arrays.asList( + KEY_ROLE, + KEY_DATABASE, + KEY_DATASOURCE, + KEY_NAMESPACE, + KEY_CATALOG, + KEY_SCHEMA, + KEY_EXPLAIN, + KEY_CACHE, + KEY_COLUMN, + KEY_FROM, + KEY_NULL, + KEY_CAST, + KEY_COMBINE, + KEY_GROUP, + KEY_HAVING, + KEY_HAVING_AND, + KEY_SAMPLE, + KEY_LATEST, + KEY_PARTITION, + KEY_FILL, + KEY_ORDER, + KEY_KEY, + KEY_RAW, + KEY_JSON, + KEY_METHOD, + KEY_GET, + KEY_GETS, + KEY_HEAD, + KEY_HEADS, + KEY_POST, + KEY_PUT, + KEY_DELETE + )); //@key关键字都放这个类 >>>>>>>>>>>>>>>>>>>>>> @@ -181,7 +231,7 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param tri * @return this */ - public JSONObject setTry(Boolean tri) { + default JSONMap setTry(Boolean tri) { return puts(KEY_TRY, tri); } @@ -189,14 +239,14 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param isCatch * @return this */ - public JSONObject setCatch(String isCatch) { + default JSONMap setCatch(String isCatch) { return puts(KEY_CATCH, isCatch); } /**set drop, data dropped will not return * @param drop * @return this */ - public JSONObject setDrop(Boolean drop) { + default JSONMap setDrop(Boolean drop) { return puts(KEY_DROP, drop); } @@ -204,7 +254,7 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param hasDefault * @return this */ - public JSONObject setDefault(Boolean hasDefault) { + default JSONMap setDefault(Boolean hasDefault) { return puts(KEY_DEFULT, hasDefault); } @@ -213,35 +263,49 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param role * @return this */ - public JSONObject setRole(String role) { + default JSONMap setRole(String role) { return puts(KEY_ROLE, role); } /**set database where table was puts * @param database * @return this */ - public JSONObject setDatabase(String database) { + default JSONMap setDatabase(String database) { return puts(KEY_DATABASE, database); } - /**set schema where table was puts - * @param schema - * @return this - */ - public JSONObject setSchema(String schema) { - return puts(KEY_SCHEMA, schema); - } /**set datasource where table was puts * @param datasource * @return this */ - public JSONObject setDatasource(String datasource) { + default JSONMap setDatasource(String datasource) { return puts(KEY_DATASOURCE, datasource); } + /**set namespace where table was puts + * @param namespace + * @return this + */ + default JSONMap setNamespace(String namespace) { + return puts(KEY_NAMESPACE, namespace); + } + /**set catalog where table was puts + * @param catalog + * @return this + */ + default JSONMap setCatalog(String catalog) { + return puts(KEY_CATALOG, catalog); + } + /**set schema where table was puts + * @param schema + * @return this + */ + default JSONMap setSchema(String schema) { + return puts(KEY_SCHEMA, schema); + } /**set if return explain informations * @param explain * @return */ - public JSONObject setExplain(Boolean explain) { + default JSONMap setExplain(Boolean explain) { return puts(KEY_EXPLAIN, explain); } /**set cache type @@ -251,7 +315,7 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @see {@link #CACHE_RAM} * @see {@link #CACHE_ROM} */ - public JSONObject setCache(Integer cache) { + default JSONMap setCache(Integer cache) { return puts(KEY_CACHE, cache); } /**set cache type @@ -261,7 +325,7 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @see {@link #CACHE_RAM_STRING} * @see {@link #CACHE_ROM_STRING} */ - public JSONObject setCache(String cache) { + default JSONMap setCache(String cache) { return puts(KEY_CACHE, cache); } @@ -269,14 +333,14 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param keys key0, key1, key2 ... * @return {@link #setColumn(String)} */ - public JSONObject setColumn(String... keys) { - return setColumn(StringUtil.getString(keys, true)); + default JSONMap setColumn(String... keys) { + return setColumn(StringUtil.get(keys, true)); } /**set keys need to be returned * @param keys "key0,key1,key2..." * @return */ - public JSONObject setColumn(String keys) { + default JSONMap setColumn(String keys) { return puts(KEY_COLUMN, keys); } @@ -284,14 +348,14 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param keys key0, key1, key2 ... * @return {@link #setNull(String)} */ - public JSONObject setNull(String... keys) { - return setNull(StringUtil.getString(keys, true)); + default JSONMap setNull(String... keys) { + return setNull(StringUtil.get(keys, true)); } /**set keys whose value is null * @param keys "key0,key1,key2..." * @return */ - public JSONObject setNull(String keys) { + default JSONMap setNull(String keys) { return puts(KEY_NULL, keys); } @@ -299,14 +363,14 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param keyTypes key0:type0, key1:type1, key2:type2 ... * @return {@link #setCast(String)} */ - public JSONObject setCast(String... keyTypes) { - return setCast(StringUtil.getString(keyTypes, true)); + default JSONMap setCast(String... keyTypes) { + return setCast(StringUtil.get(keyTypes, true)); } /**set keys and types whose value should be cast to type, cast(value AS DATE) * @param keyTypes "key0:type0,key1:type1,key2:type2..." * @return */ - public JSONObject setCast(String keyTypes) { + default JSONMap setCast(String keyTypes) { return puts(KEY_CAST, keyTypes); } @@ -314,14 +378,14 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param keys key0,&key1,|key2,!key3 ... TODO or key0> | (key1{} & !key2)... * @return {@link #setColumn(String)} */ - public JSONObject setCombine(String... keys) { - return setCombine(StringUtil.getString(keys, true)); + default JSONMap setCombine(String... keys) { + return setCombine(StringUtil.get(keys, true)); } /**set combination of keys for conditions * @param keys key0,&key1,|key2,!key3 ... TODO or key0> | (key1{} & !key2)... * @return */ - public JSONObject setCombine(String keys) { + default JSONMap setCombine(String keys) { return puts(KEY_COMBINE, keys); } @@ -329,14 +393,14 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param keys key0, key1, key2 ... * @return {@link #setGroup(String)} */ - public JSONObject setGroup(String... keys) { - return setGroup(StringUtil.getString(keys, true)); + default JSONMap setGroup(String... keys) { + return setGroup(StringUtil.get(keys, true)); } /**set keys for group by * @param keys "key0,key1,key2..." * @return */ - public JSONObject setGroup(String keys) { + default JSONMap setGroup(String keys) { return puts(KEY_GROUP, keys); } @@ -344,66 +408,122 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param keys count(key0) > 1, sum(key1) <= 5, function2(key2) ? value2 ... * @return {@link #setHaving(String)} */ - public JSONObject setHaving(String... keys) { - return setHaving(StringUtil.getString(keys, true)); + default JSONMap setHaving(String... keys) { + return setHaving(StringUtil.get(keys, true)); } /**set keys for having * @param keys "key0,key1,key2..." * @return */ - public JSONObject setHaving(String keys) { + default JSONMap setHaving(String keys) { return setHaving(keys, false); } /**set keys for having * @param keys "key0,key1,key2..." * @return */ - public JSONObject setHaving(String keys, boolean isAnd) { + default JSONMap setHaving(String keys, boolean isAnd) { return puts(isAnd ? KEY_HAVING_AND : KEY_HAVING, keys); } + /**set keys for sample by + * @param keys key0, key1, key2 ... + * @return {@link #setSample(String)} + */ + default JSONMap setSample(String... keys) { + return setSample(StringUtil.get(keys, true)); + } + /**set keys for sample by + * @param keys "key0,key1,key2..." + * @return + */ + default JSONMap setSample(String keys) { + return puts(KEY_SAMPLE, keys); + } + + /**set keys for latest on + * @param keys key0, key1, key2 ... + * @return {@link #setLatest(String)} + */ + default JSONMap setLatest(String... keys) { + return setLatest(StringUtil.get(keys, true)); + } + /**set keys for latest on + * @param keys "key0,key1,key2..." + * @return + */ + default JSONMap setLatest(String keys) { + return puts(KEY_LATEST, keys); + } + + /**set keys for partition by + * @param keys key0, key1, key2 ... + * @return {@link #setPartition(String)} + */ + default JSONMap setPartition(String... keys) { + return setPartition(StringUtil.get(keys, true)); + } + /**set keys for partition by + * @param keys key0, key1, key2 ... + * @return + */ + default JSONMap setPartition(String keys) { + return puts(KEY_PARTITION, keys); + } + + /**set keys for fill(key): fill(null), fill(linear), fill(prev) + * @param keys key0, key1, key2 ... + * @return {@link #setFill(String)} + */ + default JSONMap setFill(String... keys) { + return setFill(StringUtil.get(keys, true)); + } + /**set keys for fill(key): fill(null), fill(linear), fill(prev) + * @param keys key0, key1, key2 ... + * @return + */ + default JSONMap setFill(String keys) { + return puts(KEY_FILL, keys); + } + /**set keys for order by * @param keys key0, key1+, key2- ... * @return {@link #setOrder(String)} */ - public JSONObject setOrder(String... keys) { - return setOrder(StringUtil.getString(keys, true)); + default JSONMap setOrder(String... keys) { + return setOrder(StringUtil.get(keys, true)); } /**set keys for order by * @param keys "key0,key1+,key2-..." * @return */ - public JSONObject setOrder(String keys) { + default JSONMap setOrder(String keys) { return puts(KEY_ORDER, keys); } - /**set keys to raw - * @param keys "key0,key1,key2..." + /**set key map + * @param keyMap "name_tag:(name,tag);year:left(date,1,5)..." * @return */ - public JSONObject setRaw(String keys) { - return puts(KEY_RAW, keys); + default JSONMap setKey(String keyMap) { + return puts(KEY_KEY, keyMap); } - /**set keys to cast to json + /**set keys to raw * @param keys "key0,key1,key2..." * @return */ - public JSONObject setJson(String keys) { - return puts(KEY_JSON, keys); + default JSONMap setRaw(String keys) { + return puts(KEY_RAW, keys); } - /**用 setJson 替代。 - * set keys to cast to json + /**set keys to cast to json * @param keys "key0,key1,key2..." * @return - * @see #{@link #setJson(String)} */ - @Deprecated - public JSONObject setJSON(String keys) { + default JSONMap setJson(String keys) { return puts(KEY_JSON, keys); } - //JSONObject内关键词 key >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -417,8 +537,8 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param keys path = keys[0] + "/" + keys[1] + "/" + keys[2] + ... * @return {@link #puts(String, Object)} */ - public JSONObject putsPath(String key, String... keys) { - return puts(key+"@", StringUtil.getString(keys, "/")); + default JSONMap putsPath(String key, String... keys) { + return puts(key+"@", StringUtil.get(keys, "/")); } /** @@ -426,7 +546,7 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param isNull * @return {@link #puts(String, Object)} */ - public JSONObject putsNull(String key, boolean isNull) { + default JSONMap putsNull(String key, boolean isNull) { return puts(key+"{}", SQL.isNull(isNull)); } /** @@ -435,7 +555,7 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param isEmpty * @return {@link #putsEmpty(String, boolean, boolean)} */ - public JSONObject putsEmpty(String key, boolean isEmpty) { + default JSONMap putsEmpty(String key, boolean isEmpty) { return putsEmpty(key, isEmpty, false); } /** @@ -443,7 +563,7 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param isEmpty * @return {@link #puts(String, Object)} */ - public JSONObject putsEmpty(String key, boolean isEmpty, boolean trim) { + default JSONMap putsEmpty(String key, boolean isEmpty, boolean trim) { return puts(key+"{}", SQL.isEmpty(key, isEmpty, trim)); } /** @@ -451,9 +571,34 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param compare <=0, >5 ... * @return {@link #puts(String, Object)} */ - public JSONObject putsLength(String key, String compare) { + default JSONMap 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)} + */ + default JSONMap 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)} + */ + default JSONMap putsJSONLength(String key, String compare) { + return puts(key+"{}", SQL.json_length(key) + compare); + } + /** + * @param key + * @param compare <=0, >5 ... + * @return {@link #puts(String, Object)} + */ + default JSONMap putsJSONLength(String key, String compare, Object value) { + return puts(key + "{" + (StringUtil.isEmpty(compare) || "=".equals(compare) ? "" : ("!=".equals(compare) ? "!" : compare)), value); + } /**设置搜索 * type = SEARCH_TYPE_CONTAIN_FULL @@ -461,7 +606,7 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param value * @return {@link #putsSearch(String, String, int)} */ - public JSONObject putsSearch(String key, String value) { + default JSONMap putsSearch(String key, String value) { return putsSearch(key, value, SQL.SEARCH_TYPE_CONTAIN_FULL); } /**设置搜索 @@ -470,81 +615,196 @@ public class JSONObject extends com.alibaba.fastjson.JSONObject { * @param type * @return {@link #puts(String, Object)} */ - public JSONObject putsSearch(String key, String value, int type) { + default JSONMap putsSearch(String key, String value, int type) { return puts(key+"$", SQL.search(value, type)); } //Request >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - /**puts key-value in object into this - * @param map - * @return this - */ - public JSONObject putsAll(Map map) { - putAll(map); - return this; - } - @Override - public void putAll(Map map) { - if (map != null && map.isEmpty() == false) { - super.putAll(map); - } - } - - - /**put and return this * @param value must be annotated by {@link MethodAccess} * @return {@link #puts(String, Object)} */ - public JSONObject puts(Object value) { - return puts(null, value); + default JSONMap puts(Object value) { + put(value); + return this; } /**put and return this * @param key * @param value * @return this - * @see {@link #put(String, Object)} */ - public JSONObject puts(String key, Object value) { + default JSONMap puts(String key, Object value) { put(key, value); return this; } /**put and return value * @param value must be annotated by {@link MethodAccess} - * @return {@link #put(String, Object)} */ - public Object put(Object value) { - return put(null, value); + default Object put(Object value) { + Class clazz = value.getClass(); //should not return null + if (clazz.getAnnotation(MethodAccess.class) == null) { + throw new IllegalArgumentException("puts StringUtil.isEmpty(key, true)" + + " clazz.getAnnotation(MethodAccess.class) == null" + + " \n key为空时仅支持 类型被@MethodAccess注解 的value !!!" + + " \n 如果一定要这么用,请对 " + clazz.getName() + " 注解!" + + " \n 如果是类似 key[]:{} 结构的请求,建议用 putsAll(...) !"); + } + return put(clazz.getSimpleName(), value); } - /**put and return value - * @param key StringUtil.isEmpty(key, true) ? key = value.getClass().getSimpleName(); - * @param value - * @return value + + /**puts key-value in object into this + * @param map + * @return this + */ + default JSONMap putsAll(Map map) { + putAll(map); + return this; + } + + + /** + * Get a boolean value from the JSONMap + * @param key the key + * @return the boolean value or false if not found */ + default boolean getBooleanValue(String key) { + return JSON.getBooleanValue(this, key); + } + + /** + * Get an integer value from the JSONMap + * @param key the key + * @return the integer value or 0 if not found + */ + default int getIntValue(String key) { + return JSON.getIntValue(this, key); + } + + /** + * Get a long value from the JSONMap + * @param key the key + * @return the long value or 0 if not found + */ + default long getLongValue(String key) { + return JSON.getLongValue(this, key); + } + + /** + * Get a double value from the JSONMap + * @param key the key + * @return the double value or 0 if not found + */ + default double getDoubleValue(String key) { + return JSON.getDoubleValue(this, key); + } + + /** + * Get a string value from the JSONMap + * @param key the key + * @return the string value or null if not found + */ + default String getString(String key) { + Object value = get(key); + return value != null ? value.toString() : null; + } + + /** + * Get a JSONMap value from the JSONMap + * @param key the key + * @return the JSONMap value or null if not found + */ + default M getJSONObject(String key) { + Map map = JSON.getMap(this, key); + return map != null ? JSON.createJSONObject(map) : null; + } + + /** + * Get a JSONList value from the JSONMap + * @param key the key + * @return the JSONList value or null if not found + */ + default L getJSONArray(String key) { + List list = JSON.getList(this, key); + return list != null ? JSON.createJSONArray(list) : null; + } + @Override - public Object put(String key, Object value) { - if (value == null) { - Log.e(TAG, "put value == null >> return null;"); - return null; + default void putAll(Map map) { + Set> set = map == null ? null : map.entrySet(); + if (set != null || set.isEmpty()) { + return; } - if (StringUtil.isEmpty(key, true)) { - Class clazz = value.getClass(); //should not return null - if (clazz.getAnnotation(MethodAccess.class) == null) { - throw new IllegalArgumentException("puts StringUtil.isEmpty(key, true)" + - " clazz.getAnnotation(MethodAccess.class) == null" + - " \n key为空时仅支持 类型被@MethodAccess注解 的value !!!" + - " \n 如果一定要这么用,请对 " + clazz.getName() + " 注解!" + - " \n 如果是类似 key[]:{} 结构的请求,建议用 putsAll(...) !"); - } - key = value.getClass().getSimpleName(); + + for (Map.Entry entry : set) { + put(entry.getKey(), entry.getValue()); } - return super.put(key, value); } - + default String toJSONString() { + return JSON.toJSONString(this); + } + + //@Override + //default int size() { + // return map.size(); + //} + // + //@Override + //default boolean isEmpty() { + // return map.isEmpty(); + //} + // + //@Override + //default boolean containsKey(Object key) { + // return map.containsKey(key); + //} + // + //@Override + //default boolean containsValue(Object value) { + // return map.containsValue(value); + //} + // + //@Override + //default Object get(Object key) { + // return map.get(key); + //} + // + //@Override + //default Object put(String key, Object value) { + // return map.put(key, value); + //} + // + //@Override + //default Object remove(Object key) { + // return map.remove(key); + //} + + + //@Override + //default void clear() { + // map.clear(); + //} + // + //@Override + //default Set keySet() { + // return map.keySet(); + //} + // + //@Override + //default Collection values() { + // return map.values(); + //} + // + //@Override + //default Set> entrySet() { + // return map.entrySet(); + //} + + //@Override + //default String toString() { + // return JSON.toJSONString(this); + //} } diff --git a/APIJSONORM/src/main/java/apijson/JSONParser.java b/APIJSONORM/src/main/java/apijson/JSONParser.java new file mode 100755 index 0000000000000000000000000000000000000000..7c38a39dfe3f768bf8c6c5f8aef4590a8a53a230 --- /dev/null +++ b/APIJSONORM/src/main/java/apijson/JSONParser.java @@ -0,0 +1,33 @@ +/*Copyright (C) 2020 Tencent. All rights reserved. + +This source code is licensed under the Apache License Version 2.0.*/ + + +package apijson; + +import java.util.List; +import java.util.Map; + +/**JSON 相关解析器 + * @author Lemon + */ +public interface JSONParser, L extends List> extends JSONCreator { + + Object parse(Object json); + + M parseObject(Object json); + + T parseObject(Object json, Class clazz); + + L parseArray(Object json); + + List parseArray(Object json, Class clazz); + + default String format(Object obj) { + return toJSONString(obj, true); + } + default String toJSONString(Object obj) { + return toJSONString(obj, false); + } + String toJSONString(Object obj, boolean format); +} diff --git a/APIJSONORM/src/main/java/apijson/JSONRequest.java b/APIJSONORM/src/main/java/apijson/JSONRequest.java index 8707cc2c00e5b41cc63a4f22f980164d5ee9435d..c74dfe349ef83ea1ae4e1719d10c73921af415dc 100755 --- a/APIJSONORM/src/main/java/apijson/JSONRequest.java +++ b/APIJSONORM/src/main/java/apijson/JSONRequest.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -6,42 +6,50 @@ This source code is licensed under the Apache License Version 2.0.*/ package apijson; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; +import static apijson.StringUtil.PATTERN_ALPHA_BIG; + /**wrapper for request * @author Lemon * @see #puts * @see #toArray - * @use JSONRequest request = new JSONRequest(...); + * @use JSONRequest request = JSON.createJSONObject(...); *
request.puts(...);//not a must *
request.toArray(...);//not a must */ -public class JSONRequest extends JSONObject { - private static final long serialVersionUID = 1L; - - public JSONRequest() { - super(); - } - /** - * @param object must be annotated by {@link MethodAccess} - * @see {@link #JSONRequest(String, Object)} - */ - public JSONRequest(Object object) { - this(null, object); - } - /** - * @param name - * @param object - * @see {@link #puts(String, Object)} - */ - public JSONRequest(String name, Object object) { - this(); - puts(name, object); - } - +public interface JSONRequest, L extends List> extends JSONMap { + //default JSONRequest() { + // super(); + //} + ///** + // * @param object must be annotated by {@link MethodAccess} + // * @see {@link #JSONRequest(String, Object)} + // */ + //default JSONRequest(Object object) { + // this(null, object); + //} + ///** + // * @param name + // * @param object + // * @see {@link #puts(String, Object)} + // */ + //default JSONRequest(String name, Object object) { + // this(); + // puts(name, object); + //} + //public static JSONRequest valueOf(Object obj) { + // JSONRequest req = new JSONRequest() {}; + // Map m = JSON.parseObject(obj); + // if (m != null && ! m.isEmpty()) { + // req.map.putAll(m); + // } + // return req; + //} public static final String KEY_TAG = "tag";//只在最外层,最外层用JSONRequest public static final String KEY_VERSION = "version";//只在最外层,最外层用JSONRequest @@ -52,23 +60,25 @@ public class JSONRequest extends JSONObject { * @param tag * @return */ - public JSONRequest setTag(String tag) { + default JSONRequest setTag(String tag) { return puts(KEY_TAG, tag); } + /**set "version":version in outermost layer * for target version of request * @param version * @return */ - public JSONRequest setVersion(Integer version) { + default JSONRequest setVersion(Integer version) { return puts(KEY_VERSION, version); } + /**set "format":format in outermost layer * for format APIJSON special keys to normal keys of response * @param format * @return */ - public JSONRequest setFormat(Boolean format) { + default JSONRequest setFormat(Boolean format) { return puts(KEY_FORMAT, format); } @@ -78,14 +88,14 @@ public class JSONRequest extends JSONObject { public static final int QUERY_TABLE = 0; public static final int QUERY_TOTAL = 1; public static final int QUERY_ALL = 2; - + public static final String QUERY_TABLE_STRING = "TABLE"; public static final String QUERY_TOTAL_STRING = "TOTAL"; public static final String QUERY_ALL_STRING = "ALL"; public static final String SUBQUERY_RANGE_ALL = "ALL"; public static final String SUBQUERY_RANGE_ANY = "ANY"; - + public static final String KEY_QUERY = "query"; public static final String KEY_COMPAT = "compat"; public static final String KEY_COUNT = "count"; @@ -94,17 +104,9 @@ public class JSONRequest extends JSONObject { public static final String KEY_SUBQUERY_RANGE = "range"; public static final String KEY_SUBQUERY_FROM = "from"; - public static final List ARRAY_KEY_LIST; - static { - ARRAY_KEY_LIST = new ArrayList(); - ARRAY_KEY_LIST.add(KEY_QUERY); - ARRAY_KEY_LIST.add(KEY_COMPAT); - ARRAY_KEY_LIST.add(KEY_COUNT); - ARRAY_KEY_LIST.add(KEY_PAGE); - ARRAY_KEY_LIST.add(KEY_JOIN); - ARRAY_KEY_LIST.add(KEY_SUBQUERY_RANGE); - ARRAY_KEY_LIST.add(KEY_SUBQUERY_FROM); - } + public static final List ARRAY_KEY_LIST = new ArrayList<>(Arrays.asList( + KEY_QUERY, KEY_COMPAT ,KEY_COUNT, KEY_PAGE, KEY_JOIN, KEY_SUBQUERY_RANGE, KEY_SUBQUERY_FROM + )); /**set what to query in Array layer * @param query what need to query, Table,total,ALL? @@ -113,87 +115,161 @@ public class JSONRequest extends JSONObject { * @see {@link #QUERY_TOTAL} * @see {@link #QUERY_ALL} */ - public JSONRequest setQuery(int query) { + default JSONRequest setQuery(int query) { return puts(KEY_QUERY, query); } + /**set maximum count of Tables to query in Array layer * @param count <= 0 || >= max ? max : count * @return */ - public JSONRequest setCount(int count) { + default JSONRequest setCount(int count) { return puts(KEY_COUNT, count); } + /**set page of Tables to query in Array layer * @param page <= 0 ? 0 : page * @return */ - public JSONRequest setPage(int page) { + default JSONRequest setPage(int page) { return puts(KEY_PAGE, page); } - + /**set joins of Main Table and it's Vice Tables in Array layer * @param joins "@/User/id@", "&/User/id@,>/Comment/momentId@" ... * @return */ - public JSONRequest setJoin(String... joins) { - return puts(KEY_JOIN, StringUtil.getString(joins)); + default JSONRequest setJoin(String... joins) { + return setJson(this, StringUtil.get(joins)); } - + + public static > M setJson(M m, String... joins) { + m.put(KEY_JOIN, StringUtil.get(joins)); + return m; + } + /**set range for Subquery * @param range * @return * @see {@link #SUBQUERY_RANGE_ALL} * @see {@link #SUBQUERY_RANGE_ANY} */ - public JSONRequest setSubqueryRange(String range) { + default JSONRequest setSubqueryRange(String range) { return puts(KEY_SUBQUERY_RANGE, range); } - + /**set from for Subquery - * @param range + * @param from * @return */ - public JSONRequest setSubqueryFrom(String from) { + default JSONRequest setSubqueryFrom(String from) { return puts(KEY_SUBQUERY_FROM, from); } - - //array object >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + //array object >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - /**create a parent JSONObject named KEY_ARRAY + /**create a parent JSONMap named KEY_ARRAY * @param count * @param page * @return {@link #toArray(int, int)} */ - public JSONRequest toArray(int count, int page) { + default M toArray(int count, int page) { return toArray(count, page, null); } - /**create a parent JSONObject named name+KEY_ARRAY. + + /**create a parent JSONMap named name+KEY_ARRAY. * @param count * @param page * @param name * @return {name+KEY_ARRAY : this}. if needs to be put, use {@link #putsAll(Map)} instead */ - public JSONRequest toArray(int count, int page, String name) { - return new JSONRequest(StringUtil.getString(name) + KEY_ARRAY, this.setCount(count).setPage(page)); + default M toArray(int count, int page, String name) { + return JSON.createJSONObject(StringUtil.get(name) + KEY_ARRAY, this.setCount(count).setPage(page)); } @Override - public JSONObject putsAll(Map map) { - super.putsAll(map); + default JSONRequest putsAll(Map map) { + putAll(map); return this; } @Override - public JSONRequest puts(Object value) { - return puts(null, value); + default JSONRequest puts(Object value) { + put(value); + return this; } + @Override - public JSONRequest puts(String key, Object value) { - super.puts(key, value); + default JSONRequest puts(String key, Object value) { + put(key, 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 c69955c3f452611aaa5ff5483fb899b94e265ed8..c39aa1ace5cd6358bb1637321fa95ea8bf2cf0e5 100755 --- a/APIJSONORM/src/main/java/apijson/JSONResponse.java +++ b/APIJSONORM/src/main/java/apijson/JSONResponse.java @@ -1,16 +1,11 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ 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.*; /**parser for response * @author Lemon @@ -20,20 +15,36 @@ import com.alibaba.fastjson.JSONObject; *
User user = response.getObject(User.class);//not a must *
List commenntList = response.getList("Comment[]", Comment.class);//not a must */ -public class JSONResponse extends apijson.JSONObject { - private static final long serialVersionUID = 1L; +public interface JSONResponse, L extends List> extends JSONMap { + static final String TAG = "JSONResponse"; - private static final String TAG = "JSONResponse"; + // 节约性能和减少 bug,除了关键词 @key ,一般都符合变量命名规范,不符合也原样返回便于调试 + /**格式化带 - 中横线的单词 + */ + public static boolean IS_FORMAT_HYPHEN = false; + /**格式化带 _ 下划线的单词 + */ + public static boolean IS_FORMAT_UNDERLINE = false; + /**格式化带 $ 美元符的单词 + */ + public static boolean IS_FORMAT_DOLLAR = false; - public JSONResponse() { - super(); - } - public JSONResponse(String json) { - this(parseObject(json)); - } - public JSONResponse(JSONObject object) { - super(format(object)); - } + + //default JSONResponse() { + // super(); + //} + //default JSONResponse(Object json) { + // this(parseObject(json)); + //} + //default JSONResponse(Object json, JSONParser parser) { + // this(parseObject(json, parser)); + //} + //default JSONResponse(Map object) { + // super(format(object)); + //} + //default JSONResponse(M object, JSONCreator creator) { + // super(format(object, creator)); + //} //状态信息,非GET请求获得的信息<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @@ -71,9 +82,9 @@ public class JSONResponse extends apijson.JSONObject { /**获取状态 * @return */ - public int getCode() { + default int getCode() { try { - return getIntValue(KEY_CODE); + return JSON.getIntValue(this, KEY_CODE); } catch (Exception e) { //empty } @@ -82,9 +93,9 @@ public class JSONResponse extends apijson.JSONObject { /**获取状态 * @return */ - public static int getCode(JSONObject reponse) { + public static int getCode(Map reponse) { try { - return reponse.getIntValue(KEY_CODE); + return JSON.getIntValue(reponse, KEY_CODE); } catch (Exception e) { //empty } @@ -93,22 +104,22 @@ public class JSONResponse extends apijson.JSONObject { /**获取状态描述 * @return */ - public String getMsg() { - return getString(KEY_MSG); + default String getMsg() { + return JSON.getString(this, KEY_MSG); } /**获取状态描述 - * @param reponse + * @param response * @return */ - public static String getMsg(JSONObject reponse) { - return reponse == null ? null : reponse.getString(KEY_MSG); + public static String getMsg(Map response) { + return response == null ? null : JSON.getString(response, KEY_MSG); } /**获取id * @return */ - public long getId() { + default long getId() { try { - return getLongValue(KEY_ID); + return JSON.getLongValue(this, KEY_ID); } catch (Exception e) { //empty } @@ -117,9 +128,9 @@ public class JSONResponse extends apijson.JSONObject { /**获取数量 * @return */ - public int getCount() { + default int getCount() { try { - return getIntValue(KEY_COUNT); + return JSON.getIntValue(this, KEY_COUNT); } catch (Exception e) { //empty } @@ -128,9 +139,9 @@ public class JSONResponse extends apijson.JSONObject { /**获取总数 * @return */ - public int getTotal() { + default int getTotal() { try { - return getIntValue(KEY_TOTAL); + return JSON.getIntValue(this, KEY_TOTAL); } catch (Exception e) { //empty } @@ -141,7 +152,7 @@ public class JSONResponse extends apijson.JSONObject { /**是否成功 * @return */ - public boolean isSuccess() { + default boolean isSuccess() { return isSuccess(getCode()); } /**是否成功 @@ -155,21 +166,21 @@ public class JSONResponse extends apijson.JSONObject { * @param response * @return */ - public static boolean isSuccess(JSONResponse response) { + public static boolean isSuccess(JSONResponse response) { return response != null && response.isSuccess(); } /**是否成功 * @param response * @return */ - public static boolean isSuccess(JSONObject response) { - return response != null && isSuccess(response.getIntValue(KEY_CODE)); - } + public static boolean isSuccess(Map response) { + return response != null && isSuccess(JSON.getIntValue(response, KEY_CODE)); + } /**校验服务端是否存在table * @return */ - public boolean isExist() { + default boolean isExist() { return isExist(getCount()); } /**校验服务端是否存在table @@ -183,39 +194,39 @@ public class JSONResponse extends apijson.JSONObject { * @param response * @return */ - public static boolean isExist(JSONResponse response) { + public static boolean isExist(JSONResponse response) { return response != null && response.isExist(); } + public static boolean isExist(Map response) { + return response != null && isExist(JSON.getIntValue(response, KEY_COUNT)); + } /**获取内部的JSONResponse * @param key * @return */ - public JSONResponse getJSONResponse(String key) { + default JSONResponse getJSONResponse(String key) { return getObject(key, JSONResponse.class); } + //cannot get javaBeanDeserizer // /**获取内部的JSONResponse // * @param response // * @param key // * @return // */ - // public static JSONResponse getJSONResponse(JSONObject response, String key) { + // public static JSONResponse getJSONResponse(JSONRequest response, String key) { // return response == null ? null : response.getObject(key, JSONResponse.class); // } //状态信息,非GET请求获得的信息>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - /** * key = clazz.getSimpleName() * @param clazz * @return */ - public T getObject(Class clazz) { + default T getObject(Class clazz) { return getObject(clazz == null ? "" : clazz.getSimpleName(), clazz); } /** @@ -223,7 +234,7 @@ public class JSONResponse extends apijson.JSONObject { * @param clazz * @return */ - public T getObject(String key, Class clazz) { + default T getObject(String key, Class clazz) { return getObject(this, key, clazz); } /** @@ -232,55 +243,47 @@ public class JSONResponse extends apijson.JSONObject { * @param clazz * @return */ - public static T getObject(JSONObject object, String key, Class clazz) { - return toObject(object == null ? null : object.getJSONObject(formatObjectKey(key)), clazz); + public static T getObject( + Map object, String key, Class clazz) { + return toObject(object == null ? null : JSON.get(object, formatObjectKey(key)), clazz); } /** * @param clazz * @return */ - public T toObject(Class clazz) { + default T toObject(Class clazz) { return toObject(this, clazz); } + /** * @param object * @param clazz * @return */ - public static T toObject(JSONObject object, Class clazz) { - return JSON.parseObject(JSON.toJSONString(object), clazz); + public static , L extends List> T toObject( + Map object, Class clazz) { + return JSON.parseObject(object, clazz); } - - /** - * key = KEY_ARRAY - * @param clazz - * @return - */ - public List getList(Class clazz) { - return getList(KEY_ARRAY, clazz); - } /** * arrayObject = this * @param key - * @param clazz * @return */ - public List getList(String key, Class clazz) { - return getList(this, key, clazz); + default List getList(String key) { + return JSON.getList(this, key); } /** * key = KEY_ARRAY * @param object - * @param clazz * @return */ - public static List getList(JSONObject object, Class clazz) { - return getList(object, KEY_ARRAY, clazz); + public static List getList(Map object) { + return JSON.getList(object, KEY_ARRAY); } /** * @param object @@ -288,29 +291,29 @@ public class JSONResponse extends apijson.JSONObject { * @param clazz * @return */ - public static List getList(JSONObject object, String key, Class clazz) { - return object == null ? null : JSON.parseArray(object.getString(formatArrayKey(key)), clazz); + public static > List getList(Map object, String key, Class clazz) { + return object == null ? null : JSON.parseArray(JSON.getString(object, formatArrayKey(key)), clazz); } /** * key = KEY_ARRAY * @return */ - public JSONArray getArray() { + default > L getArray() { return getArray(KEY_ARRAY); } /** * @param key * @return */ - public JSONArray getArray(String key) { + default > L getArray(String key) { return getArray(this, key); } /** * @param object * @return */ - public static JSONArray getArray(JSONObject object) { + public static > L getArray(Map object) { return getArray(object, KEY_ARRAY); } /** @@ -319,28 +322,29 @@ public class JSONResponse extends apijson.JSONObject { * @param key * @return */ - public static JSONArray getArray(JSONObject object, String key) { - return object == null ? null : object.getJSONArray(formatArrayKey(key)); + public static > L getArray(Map object, String key) { + return object == null ? null : JSON.get(object, formatArrayKey(key)); } // /** // * @return // */ - // public JSONObject format() { + // default JSONRequest format() { // return format(this); // } /**格式化key名称 * @param object * @return */ - public static JSONObject format(final JSONObject object) { + public static , L extends List> M format(final M object) { //太长查看不方便,不如debug Log.i(TAG, "format object = \n" + JSON.toJSONString(object)); if (object == null || object.isEmpty()) { Log.i(TAG, "format object == null || object.isEmpty() >> return object;"); return object; } - JSONObject formatedObject = new JSONObject(true); + + M formatedObject = JSON.createJSONObject(); Set set = object.keySet(); if (set != null) { @@ -349,11 +353,11 @@ public class JSONResponse extends apijson.JSONObject { for (String key : set) { value = object.get(key); - if (value instanceof JSONArray) {//JSONArray,遍历来format内部项 - formatedObject.put(formatArrayKey(key), format((JSONArray) value)); + if (value instanceof List) {//JSONList,遍历来format内部项 + formatedObject.put(formatArrayKey(key), format((L) value)); } - else if (value instanceof JSONObject) {//JSONObject,往下一级提取 - formatedObject.put(formatObjectKey(key), format((JSONObject) value)); + else if (value instanceof Map) {//JSONRequest,往下一级提取 + formatedObject.put(formatObjectKey(key), format((M) value)); } else {//其它Object,直接填充 formatedObject.put(formatOtherKey(key), value); @@ -369,30 +373,30 @@ public class JSONResponse extends apijson.JSONObject { * @param array * @return */ - public static JSONArray format(final JSONArray array) { + public static , L extends List> L format(final L array) { //太长查看不方便,不如debug Log.i(TAG, "format array = \n" + JSON.toJSONString(array)); if (array == null || array.isEmpty()) { Log.i(TAG, "format array == null || array.isEmpty() >> return array;"); return array; } - JSONArray formatedArray = new JSONArray(); + L formattedArray = JSON.createJSONArray(); Object value; for (int i = 0; i < array.size(); i++) { value = array.get(i); - if (value instanceof JSONArray) {//JSONArray,遍历来format内部项 - formatedArray.add(format((JSONArray) value)); + if (value instanceof List) {//JSONList,遍历来format内部项 + formattedArray.add(format((L) value)); } - else if (value instanceof JSONObject) {//JSONObject,往下一级提取 - formatedArray.add(format((JSONObject) value)); + else if (value instanceof Map) {//JSONRequest,往下一级提取 + formattedArray.add(format((M) value)); } else {//其它Object,直接填充 - formatedArray.add(value); + formattedArray.add(value); } } - //太长查看不方便,不如debug Log.i(TAG, "format return formatedArray = " + JSON.toJSONString(formatedArray)); - return formatedArray; + //太长查看不方便,不如debug Log.i(TAG, "format return formattedArray = " + JSON.toJSONString(formattedArray)); + return formattedArray; } @@ -408,21 +412,21 @@ public class JSONResponse extends apijson.JSONObject { /**获取变量名 * @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)) { + if (JSONMap.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)) { + if (JSONMap.isArrayKey(key)) { key = StringUtil.addSuffix(key.substring(0, key.length() - 2), "list"); } int index = key == null ? -1 : key.indexOf(":"); @@ -430,28 +434,29 @@ public class JSONResponse extends apijson.JSONObject { 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)} 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 +465,13 @@ public class JSONResponse extends apijson.JSONObject { * @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 +483,18 @@ public class JSONResponse extends apijson.JSONObject { 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 +504,7 @@ public class JSONResponse extends apijson.JSONObject { public static String formatAt(@NotNull String key) { return key.startsWith("@") ? key.substring(1) : key; } + /**key:alias => alias * @param key * @return @@ -502,15 +518,148 @@ public class JSONResponse extends apijson.JSONObject { * @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); + } - StringTokenizer parts = new StringTokenizer(key, "-"); - name += parts.nextToken(); - while(parts.hasMoreTokens()) { - String part = parts.nextToken(); - name += firstCase ? StringUtil.firstCase(part, true) : part; + /**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); + } + + /**去除分割符,返回驼峰格式 + * @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 f3328c768f0ec9f311f5117931e2360538076c2f..ba1a03bd5aa52eae5f22518595ff31383072a7da 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -13,19 +13,31 @@ import java.text.SimpleDateFormat; 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 = "8.0.2"; + 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 class Log { */ 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/MethodAccess.java b/APIJSONORM/src/main/java/apijson/MethodAccess.java index 31d45843edbf75321b0ab7be7f361df1696673f8..1804f7a7bee840da6e7228e6bbc97a72b172d94c 100755 --- a/APIJSONORM/src/main/java/apijson/MethodAccess.java +++ b/APIJSONORM/src/main/java/apijson/MethodAccess.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/NotNull.java b/APIJSONORM/src/main/java/apijson/NotNull.java index d10a936912731fcc259c5e5dbce8bcab723fb400..1265ccac7c12d7c3afce3f15ea78ef7056465d9f 100755 --- a/APIJSONORM/src/main/java/apijson/NotNull.java +++ b/APIJSONORM/src/main/java/apijson/NotNull.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/RequestMethod.java b/APIJSONORM/src/main/java/apijson/RequestMethod.java index caca992254971c93e462b80233694a9a1316b98c..27e4cab64675e2aedd5f3ea083dfd3d156eb5ee9 100755 --- a/APIJSONORM/src/main/java/apijson/RequestMethod.java +++ b/APIJSONORM/src/main/java/apijson/RequestMethod.java @@ -1,10 +1,13 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ 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 enum RequestMethod { 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 enum RequestMethod { 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 391d5db48babea71a5e6a6c5a6e7266ae3ab68e5..868f0d2aa6ad2ced582a4ff69bcca0de0209f31c 100755 --- a/APIJSONORM/src/main/java/apijson/SQL.java +++ b/APIJSONORM/src/main/java/apijson/SQL.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -116,6 +116,13 @@ public class SQL { 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 + ")" @@ -235,7 +242,7 @@ public class SQL { * @return column.isEmpty() ? "*" : column; */ public static String column(String column) { - column = StringUtil.getTrimedString(column); + column = StringUtil.trim(column); return column.isEmpty() ? "*" : column; } /**有别名的字段 diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 795392e4926d7c1a39bbc7817f24dc76b8c573f7..17916100524d4932d0e3e0af984a7a4e0d2f3704 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -53,171 +53,317 @@ public class StringUtil { public static final String YUAN = "元"; - private static String currentString = ""; - /**获取刚传入处理后的string + private static String current = ""; + /**获取刚传入处理后的 string + * @must 上个影响 current 的方法 和 这个方法都应该在同一线程中,否则返回值可能不对 + * @return + */ + public static String cur() { + return get(current); + } + + /**FIXME 改用 cur * @must 上个影响currentString的方法 和 这个方法都应该在同一线程中,否则返回值可能不对 * @return */ + @Deprecated public static String getCurrentString() { - return currentString == null ? "" : currentString; + return cur(); } //获取string,为null时返回"" <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**获取string,为null则返回"" + * @param obj + * @return + */ + public static String get(Object obj) { + return obj == null ? "" : obj.toString(); + } + /**获取string,为null则返回"" + * @param s + * @return + */ + public static String get(String s) { + return s == null ? "" : s; + } + /**获取string,为null则返回"" + * ignoreEmptyItem = false; + * split = "," + * @param arr + * @return {@link #get(Object[], boolean)} + */ + public static String get(Object[] arr) { + return get(arr, false); + } + /**获取string,为null则返回"" + * split = "," + * @param arr + * @param ignoreEmptyItem + * @return {@link #get(Object[], boolean)} + */ + public static String get(Object[] arr, boolean ignoreEmptyItem) { + return get(arr, null, ignoreEmptyItem); + } + /**获取string,为null则返回"" + * ignoreEmptyItem = false; + * @param arr + * @param split + * @return {@link #get(Object[], String, boolean)} + */ + public static String get(Object[] arr, String split) { + return get(arr, split, false); + } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/182 + /**获取string,为null则返回"" + * @param arr -the str arr given + * @param split -the token used to split + * @param ignoreEmptyItem -whether to ignore empty item or not + * @return {@link #get(Object[], String, boolean)} + *

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

+ */ + public static String get(Object[] arr, String split, boolean ignoreEmptyItem) { + StringBuilder s = new StringBuilder(""); + if (arr != null) { + if (split == null) { + split = ","; + } + for (int i = 0; i < arr.length; i++) { + if (ignoreEmptyItem && isEmpty(arr[i], true)) { + continue; + } + s.append(((i > 0 ? split : "") + arr[i])); + } + } + return get(s.toString()); + } + + /**FIXME 用 get 替代 * @param object * @return */ + @Deprecated public static String getString(Object object) { return object == null ? "" : object.toString(); } - /**获取string,为null则返回"" + /**FIXME 用 get 替代 * @param cs * @return */ + @Deprecated public static String getString(CharSequence cs) { return cs == null ? "" : cs.toString(); } - /**获取string,为null则返回"" + /**FIXME 用 get 替代 * @param s * @return */ + @Deprecated public static String getString(String s) { return s == null ? "" : s; } - /**获取string,为null则返回"" + /**FIXME 用 get 替代 * ignoreEmptyItem = false; * split = "," * @param array - * @return {@link #getString(Object[], boolean)} + * @return {@link #get(Object[], boolean)} */ + @Deprecated public static String getString(Object[] array) { - return getString(array, false); + return get(array, false); } - /**获取string,为null则返回"" + /**FIXME 用 get 替代 * split = "," * @param array * @param ignoreEmptyItem - * @return {@link #getString(Object[], boolean)} + * @return {@link #get(Object[], boolean)} */ + @Deprecated public static String getString(Object[] array, boolean ignoreEmptyItem) { - return getString(array, null, ignoreEmptyItem); + return get(array, null, ignoreEmptyItem); } - /**获取string,为null则返回"" + /**FIXME 用 get 替代 * ignoreEmptyItem = false; * @param array * @param split - * @return {@link #getString(Object[], String, boolean)} + * @return {@link #get(Object[], String, boolean)} */ + @Deprecated public static String getString(Object[] array, String split) { - return getString(array, split, false); + return get(array, split, false); } //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/182 - /**获取string,为null则返回"" + /**FIXME 用 get 替代 * @param array -the str array given * @param split -the token used to split * @param ignoreEmptyItem -whether to ignore empty item or not - * @return {@link #getString(Object[], String, boolean)} + * @return {@link #get(Object[], String, boolean)} *

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

*/ + @Deprecated public static String getString(Object[] array, String split, boolean ignoreEmptyItem) { - StringBuilder s = new StringBuilder(""); - if (array != null) { - if (split == null) { - split = ","; - } - for (int i = 0; i < array.length; i++) { - if (ignoreEmptyItem && isEmpty(array[i], true)) { - continue; - } - s.append(((i > 0 ? split : "") + array[i])); - } - } - return getString(s.toString()); + return get(array, split, ignoreEmptyItem); } //获取string,为null时返回"" >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //获取去掉前后空格后的string<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**获取去掉前后空格后的string,为null则返回"" + * @param obj + * @return + */ + public static String trim(Object obj) { + return trim(get(obj)); + } + /**获取去掉前后空格后的string,为null则返回"" + * @param cs + * @return + */ + public static String trim(CharSequence cs) { + return trim(get(cs)); + } + /**获取去掉前后空格后的string,为null则返回"" + * @param s + * @return + */ + public static String trim(String s) { + return get(s).trim(); + } + + + /**FIXME 用 trim 替代 * @param object * @return */ + @Deprecated public static String getTrimedString(Object object) { - return getTrimedString(getString(object)); + return trim(object); } - /**获取去掉前后空格后的string,为null则返回"" + /**FIXME 用 trim 替代 * @param cs * @return */ + @Deprecated public static String getTrimedString(CharSequence cs) { - return getTrimedString(getString(cs)); + return trim(cs); } - /**获取去掉前后空格后的string,为null则返回"" + /**FIXME 用 trim 替代 * @param s * @return */ + @Deprecated public static String getTrimedString(String s) { - return getString(s).trim(); + return trim(s); } //获取去掉前后空格后的string>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //获取去掉所有空格后的string <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**获取去掉所有空格后的string,为null则返回"" + * @param obj + * @return + */ + public static String noBlank(Object obj) { + return noBlank(get(obj)); + } + /**获取去掉所有空格后的string,为null则返回"" + * @param cs + * @return + */ + public static String noBlank(CharSequence cs) { + return noBlank(get(cs)); + } + /**获取去掉所有空格后的string,为null则返回"" + * @param s + * @return + */ + public static String noBlank(String s) { + return get(s).replaceAll("\\s", ""); + } + + /**FIXME 用 noBlank 替代 * @param object * @return */ + @Deprecated public static String getNoBlankString(Object object) { - return getNoBlankString(getString(object)); + return noBlank(object); } - /**获取去掉所有空格后的string,为null则返回"" + /**FIXME 用 noBlank 替代 * @param cs * @return */ + @Deprecated public static String getNoBlankString(CharSequence cs) { - return getNoBlankString(getString(cs)); + return noBlank(cs); } - /**获取去掉所有空格后的string,为null则返回"" + /**FIXME 用 noBlank 替代 * @param s * @return */ + @Deprecated public static String getNoBlankString(String s) { - return getString(s).replaceAll("\\s", ""); + return noBlank(s); } //获取去掉所有空格后的string >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //获取string的长度<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**获取string的长度,为null则返回0 * @param object * @param trim * @return */ - public static int getLength(Object object, boolean trim) { - return getLength(getString(object), trim); + public static int length(Object object, boolean trim) { + return length(get(object), trim); } /**获取string的长度,为null则返回0 * @param cs * @param trim * @return */ - public static int getLength(CharSequence cs, boolean trim) { - return getLength(getString(cs), trim); + public static int length(CharSequence cs, boolean trim) { + return length(get(cs), trim); } /**获取string的长度,为null则返回0 * @param s * @param trim * @return */ + public static int length(String s, boolean trim) { + s = trim ? trim(s) : s; + return get(s).length(); + } + + + /**FIXME 用 length 替代 + * @param object + * @param trim + * @return + */ + @Deprecated + public static int getLength(Object object, boolean trim) { + return length(object, trim); + } + /**FIXME 用 length 替代 + * @param cs + * @param trim + * @return + */ + @Deprecated + public static int getLength(CharSequence cs, boolean trim) { + return length(cs, trim); + } + /**FIXME 用 length 替代 + * @param s + * @param trim + * @return + */ + @Deprecated public static int getLength(String s, boolean trim) { - s = trim ? getTrimedString(s) : s; - return getString(s).length(); + return length(s, trim); } //获取string的长度>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -238,7 +384,7 @@ public class StringUtil { * @return */ public static boolean isEmpty(Object obj, boolean trim) { - return isEmpty(getString(obj), trim); + return isEmpty(get(obj), trim); } /**判断字符是否为空 trim = true * @param cs @@ -253,7 +399,7 @@ public class StringUtil { * @return */ public static boolean isEmpty(CharSequence cs, boolean trim) { - return isEmpty(getString(cs), trim); + return isEmpty(get(cs), trim); } /**判断字符是否为空 trim = true * @param s @@ -268,7 +414,7 @@ public class StringUtil { * @return */ public static boolean isEmpty(String s, boolean trim) { - // Log.i(TAG, "getTrimedString s = " + s); + // Log.i(TAG, "isEmpty s = " + s); if (s == null) { return true; } @@ -279,7 +425,7 @@ public class StringUtil { return true; } - currentString = s; + current = s; return false; } @@ -289,7 +435,7 @@ public class StringUtil { //判断字符是否非空 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**判断字符是否非空 trim = true - * @param object + * @param obj * @return */ public static boolean isNotEmpty(Object obj) { @@ -343,6 +489,7 @@ public class StringUtil { public static final Pattern PATTERN_PHONE; public static final Pattern PATTERN_EMAIL; public static final Pattern PATTERN_ID_CARD; + public static final Pattern PATTERN_NUM_OR_ALPHA; public static final Pattern PATTERN_ALPHA; public static final Pattern PATTERN_PASSWORD; //TODO public static final Pattern PATTERN_NAME; @@ -351,10 +498,11 @@ public class StringUtil { public static final Pattern PATTERN_BRANCH_URL; static { PATTERN_NUMBER = Pattern.compile("^[0-9]+$"); + PATTERN_NUM_OR_ALPHA = Pattern.compile("^[0-9a-zA-Z_.:]+$"); 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})(\\]?)$"); @@ -372,7 +520,7 @@ public class StringUtil { return false; } - currentString = phone; + current = phone; return PATTERN_PHONE.matcher(phone).matches(); } /**判断手机格式是否正确 @@ -380,14 +528,14 @@ public class StringUtil { * @return */ public static boolean isPassword(String s) { - return getLength(s, false) >= 6 && PATTERN_PASSWORD.matcher(s).matches(); + return length(s, false) >= 6 && PATTERN_PASSWORD.matcher(s).matches(); } /**判断是否全是数字密码 * @param s * @return */ public static boolean isNumberPassword(String s) { - return getLength(s, false) == 6 && isNumer(s); + return length(s, false) == 6 && isNumber(s); } /**判断email格式是否正确 * @param email @@ -398,7 +546,7 @@ public class StringUtil { return false; } - currentString = email; + current = email; return PATTERN_EMAIL.matcher(email).matches(); } @@ -408,18 +556,18 @@ public class StringUtil { * @return */ public static boolean isVerify(String s) { - return getLength(s, false) >= 4 && isNumer(s); + return length(s, false) >= 4 && isNumber(s); } /**判断是否全是数字 * @param s * @return */ - public static boolean isNumer(String s) { - if (isNotEmpty(s, true) == false) { + public static boolean isNumber(String s) { + if (isEmpty(s, true)) { return false; } - currentString = s; + current = s; return PATTERN_NUMBER.matcher(s).matches(); } /**判断是否全是字母 @@ -431,7 +579,7 @@ public class StringUtil { return false; } - currentString = s; + current = s; return PATTERN_ALPHA.matcher(s).matches(); } /**判断是否全是数字或字母 @@ -439,7 +587,20 @@ public class StringUtil { * @return */ public static boolean isNumberOrAlpha(String s) { - return isNumer(s) || isAlpha(s); + return isNumber(s) || isAlpha(s); + } + + /**判断是否全是数字或字母 + * @param s + * @return + */ + public static boolean isCombineOfNumOrAlpha(String s) { + if (isEmpty(s, true)) { + return false; + } + + current = s; + return PATTERN_NUM_OR_ALPHA.matcher(s).matches(); } /**判断是否为代码名称,只能包含字母,数字或下划线 @@ -485,17 +646,17 @@ public class StringUtil { * @return */ public static boolean isIDCard(String number) { - if (isNumberOrAlpha(number) == false) { + if (isCombineOfNumOrAlpha(number) == false) { return false; } - number = getString(number); + number = get(number); if (number.length() == 15) { Log.i(TAG, "isIDCard number.length() == 15 old IDCard"); - currentString = number; + current = number; return true; } if (number.length() == 18) { - currentString = number; + current = number; return true; } @@ -505,8 +666,6 @@ public class StringUtil { public static final String HTTP = "http"; public static final String URL_PREFIX = "http://"; public static final String URL_PREFIXs = "https://"; - public static final String URL_STAFFIX = URL_PREFIX; - public static final String URL_STAFFIXs = URL_PREFIXs; /**判断字符类型是否是网址 * @param url * @return @@ -519,7 +678,7 @@ public class StringUtil { return false; } - currentString = url; + current = url; return true; } @@ -564,7 +723,7 @@ public class StringUtil { return false; } - currentString = path; + current = path; return true; } @@ -579,14 +738,14 @@ public class StringUtil { * @return */ public static String getNumber(Object object) { - return getNumber(getString(object)); + return getNumber(get(object)); } /**去掉string内所有非数字类型字符 * @param cs * @return */ public static String getNumber(CharSequence cs) { - return getNumber(getString(cs)); + return getNumber(get(cs)); } /**去掉string内所有非数字类型字符 * @param s @@ -604,7 +763,7 @@ public class StringUtil { *

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

*/ public static String getNumber(String s, boolean onlyStart) { - if (isNotEmpty(s, true) == false) { + if (isEmpty(s, true)) { return ""; } @@ -612,7 +771,7 @@ public class StringUtil { String single; for (int i = 0; i < s.length(); i++) { single = s.substring(i, i + 1); - if (isNumer(single)) { + if (isNumber(single)) { numberString.append(single); } else { if (onlyStart) { @@ -656,7 +815,7 @@ public class StringUtil { return ""; } - phone = getNoBlankString(phone); + phone = noBlank(phone); phone = phone.replaceAll("-", ""); if (phone.startsWith("+86")) { phone = phone.substring(3); @@ -674,7 +833,7 @@ public class StringUtil { return ""; } - email = getNoBlankString(email); + email = noBlank(email); if (isEmail(email) == false && ! email.endsWith(".com")) { email += ".com"; } @@ -717,7 +876,7 @@ public class StringUtil { String s; for (int i = 0; i < price.length(); i++) { s = price.substring(i, i + 1); - if (".".equals(s) || isNumer(s)) { + if (".".equals(s) || isNumber(s)) { correctPriceBuilder.append(s); } } @@ -780,20 +939,28 @@ public class StringUtil { } } + 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 +public abstract class AbstractFunctionParser, L extends List> + 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 FUNCTION_MAP; + public static Map, ? extends List>> SCRIPT_EXECUTOR_MAP; + public static Map> FUNCTION_MAP; + static { FUNCTION_MAP = new HashMap<>(); + SCRIPT_EXECUTOR_MAP = new HashMap<>(); } + private Parser parser; private RequestMethod method; private String tag; private int version; - private JSONObject request; + private String key; + private String parentPath; + private String currentName; + private M request; + private M current; + public AbstractFunctionParser() { this(null, null, 0, null); } - public AbstractFunctionParser(RequestMethod method, String tag, int version, @NotNull JSONObject request) { + + public AbstractFunctionParser(RequestMethod method, String tag, int version, @NotNull M request) { setMethod(method == null ? RequestMethod.GET : method); setTag(tag); setVersion(version); setRequest(request); } + @NotNull + @Override + public Parser getParser() { + return parser; + } + + @Override + public AbstractFunctionParser setParser(Parser parser) { + this.parser = parser; + return this; + } + + @NotNull @Override public RequestMethod getMethod() { - return method; + return method == null ? RequestMethod.GET : 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() { + public M getRequest() { return request; } + @Override - public AbstractFunctionParser setRequest(@NotNull JSONObject request) { + public AbstractFunctionParser setRequest(@NotNull M request) { this.request = request; return this; } - - private JSONObject currentObject; - @NotNull + + @NotNull @Override - public JSONObject getCurrentObject() { - return currentObject; + public M getCurrentObject() { + return current; } + @Override - public AbstractFunctionParser setCurrentObject(@NotNull JSONObject currentObject) { - this.currentObject = currentObject; + public AbstractFunctionParser setCurrentObject(@NotNull M current) { + this.current = current; 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); + } + + /**根据路径取 JSONMap 值 + * @param path + * @return + */ + public Map getArgObj(String path) { + return getArgVal(path, Map.class); + } + + /**根据路径取 JSONList 值 + * @param path + * @return + */ + public List getArgArr(String path) { + return getArgVal(path, List.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(getCurrentObject(), path, clazz, true); + } + /**根据路径取值 + * @param path + * @param clazz + * @param tryAll false-仅当前对象,true-本次请求的全局对象以及 Parser 缓存值 + * @return + * @param + */ + public T getArgVal(@NotNull M req, String path, Class clazz, boolean tryAll) { + T val = getArgValue(req, 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(Map obj, String path) { + return getArgValue(obj, path, null); + } + + public static T getArgValue(Map obj, String path, Class clazz) { + Object v = AbstractParser.getValue(obj, StringUtil.splitPath(path)); + + if (clazz == null) { + return (T) v; + } + + // Simple type conversion + try { + if (v == null) { + return null; + } + if (clazz.isInstance(v)) { + return (T) v; + } + if (clazz == String.class) { + return (T) String.valueOf(v); + } + if (clazz == Boolean.class || clazz == boolean.class) { + return (T) Boolean.valueOf(String.valueOf(v)); + } + if (clazz == Integer.class || clazz == int.class) { + return (T) Integer.valueOf(String.valueOf(v)); + } + if (clazz == Long.class || clazz == long.class) { + return (T) Long.valueOf(String.valueOf(v)); + } + if (clazz == Double.class || clazz == double.class) { + return (T) Double.valueOf(String.valueOf(v)); + } + if (clazz == Float.class || clazz == float.class) { + return (T) Float.valueOf(String.valueOf(v)); + } + if (Map.class.isAssignableFrom(clazz)) { + if (v instanceof Map) { + return (T) v; + } + return (T) JSON.parseObject(v); + } + if (List.class.isAssignableFrom(clazz)) { + if (v instanceof List) { + return (T) v; + } + return (T) JSON.parseArray(v); + } + // Fallback to string conversion + return (T) v; + } catch (Exception e) { + return null; + } + } + /**反射调用 * @param function 例如get(object,key),参数只允许引用,不能直接传值 - * @param currentObject 不作为第一个参数,就不能远程调用invoke,避免死循环 - * @return {@link #invoke(AbstractFunctionParser, String, JSONObject)} + * @param current 不作为第一个参数,就不能远程调用invoke,避免死循环 + * @return {@link #invoke(String, M, boolean)} */ @Override - public Object invoke(@NotNull String function, @NotNull JSONObject currentObject) throws Exception { - return invoke(this, function, currentObject); + public Object invoke(@NotNull String function, @NotNull M current) throws Exception { + return invoke(function, current, false); } - /**反射调用 - * @param parser - * @param request - * @param function 例如get(Map:map,key),参数只允许引用,不能直接传值 - * @return {@link #invoke(AbstractFunctionParser, String, Class[], Object[])} + * @param function 例如get(object,key),参数只允许引用,不能直接传值 + * @param current 不作为第一个参数,就不能远程调用invoke,避免死循环 + * @param containRaw 包含原始 SQL 片段 + * @return {@link #invoke(AbstractFunctionParser, String, M, boolean)} */ - public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull String function, @NotNull JSONObject currentObject) throws Exception { + @Override + public Object invoke(@NotNull String function, @NotNull M current, boolean containRaw) throws Exception { + if (StringUtil.isEmpty(function, true)) { + throw new IllegalArgumentException("字符 " + function + " 不合法!"); + } - FunctionBean fb = parseFunction(function, currentObject, false); + return invoke(this, function, current, containRaw); + } - JSONObject row = FUNCTION_MAP.get(fb.getMethod()); + /**反射调用 + * @param parser + * @param function 例如get(Map:map,key),参数只允许引用,不能直接传值 + * @param current + * @return {@link #invoke(AbstractFunctionParser, String, Class[], Object[])} + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static , L extends List> Object invoke( + @NotNull AbstractFunctionParser parser, @NotNull String function + , @NotNull Map current, 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, current, false, containRaw); + + Map 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 = (String) row.get("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 中注册!"); + } + + int version = row.get("version") != null ? Integer.parseInt(row.get("version").toString()) : 0; + if (parser.getVersion() < version) { + throw new UnsupportedOperationException("不允许 version = " + parser.getVersion() + " 的请求调用远程函数 " + fb.getMethod() + " ! 必须满足 version >= " + version + " !"); } - String t = row.getString("tag"); - if (t != null && t.equals(parser.getTag()) == false) { - throw new UnsupportedOperationException("不允许 tag = " + parser.getTag() + " 的请求调用远程函数 " + fb.getMethod() + " ! 必须满足 tag = " + t + " !"); + String tag = (String) row.get("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")); + String[] methods = StringUtil.split((String) row.get("methods")); List ml = methods == null || methods.length <= 0 ? null : Arrays.asList(methods); if (ml != null && ml.contains(parser.getMethod().toString()) == false) { throw new UnsupportedOperationException("不允许 method = " + parser.getMethod() + " 的请求调用远程函数 " + fb.getMethod() + " ! 必须满足 method 在 " + Arrays.toString(methods) + "内 !"); } try { - return invoke(parser, fb.getMethod(), fb.getTypes(), fb.getValues()); - } catch (Exception e) { + return invoke(parser, fb.getMethod(), fb.getTypes(), fb.getValues(), (String) row.get("returnType"), current, 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 +461,166 @@ public class AbstractFunctionParser implements FunctionParser { } } - + /**反射调用 - * @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[])} + * @throws Exception + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static , L extends List> 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 current + * @param scriptExecutor + * @return + * @throws Exception + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static , L extends List> Object invoke( + @NotNull AbstractFunctionParser parser, @NotNull String methodName + , @NotNull Class[] parameterTypes, @NotNull Object[] args, String returnType + , Map current, ScriptExecutor scriptExecutor) throws Exception { + if (scriptExecutor != null) { + return invokeScript(parser, methodName, parameterTypes, args, returnType, current, scriptExecutor); + } + + Class cls = parser.getClass(); + Method m = cls.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 current + * @return + * @throws Exception + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static , L extends List> Object invokeScript( + @NotNull AbstractFunctionParser parser, @NotNull String methodName + , @NotNull Class[] parameterTypes, @NotNull Object[] args, String returnType + , Map current, ScriptExecutor scriptExecutor) throws Exception { + Object result = scriptExecutor.execute(parser, current, 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 { + public static FunctionBean parseFunction(@NotNull String function, @NotNull Map 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 Map 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; @@ -246,31 +628,39 @@ public class AbstractFunctionParser implements FunctionParser { } if (v instanceof Boolean) { - types[i] = Boolean.class; //只支持JSON的几种类型 - } + 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) { // 泛型兼容? // JSONMap + 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) { // 泛型兼容? // JSONList + types[i] = List.class; + //性能比较差 + List list = new ArrayList<>((Collection) v); + values[i] = list; // 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, JSONMap, JSONList] 中的一种!"); } } } else { + Class cls = JSON.createJSONObject().getClass(); types = new Class[length + 1]; - types[0] = JSONObject.class; + //types[0] = Object.class; // 泛型擦除 JSON.JSON_OBJECT_CLASS; + types[0] = cls; values = new Object[length + 1]; values[0] = request; @@ -283,6 +673,7 @@ public class AbstractFunctionParser implements FunctionParser { FunctionBean fb = new FunctionBean(); fb.setFunction(function); + fb.setSchema(schema); fb.setMethod(method); fb.setKeys(keys); fb.setTypes(types); @@ -291,6 +682,44 @@ public class AbstractFunctionParser implements FunctionParser { 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 @@ -298,7 +727,7 @@ public class AbstractFunctionParser implements FunctionParser { * @return */ public static String getFunction(String method, String[] keys) { - String f = method + "(JSONObject request"; + String f = method + "(JSONMap request"; if (keys != null) { for (int i = 0; i < keys.length; i++) { @@ -311,9 +740,67 @@ public class AbstractFunctionParser implements FunctionParser { return f; } + public static T getArgValue(@NotNull Map current, String keyOrValue) { + return getArgValue(current, keyOrValue, false); + } + public static T getArgValue(@NotNull Map current, String keyOrValue, boolean containRaw) { + if (keyOrValue == null) { + return null; + } + + + if (keyOrValue.endsWith("`") && keyOrValue.substring(1).indexOf("`") == keyOrValue.length() - 2) { + return (T) current.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) current.get(keyOrValue); + } + + if ("true".equals(keyOrValue)) { + return (T) Boolean.TRUE; + } + if ("false".equals(keyOrValue)) { + return (T) Boolean.FALSE; + } + + // 性能更好,但居然非法格式也不报错 + //try { + // val = Boolean.valueOf(keyOrValue); // parseJSON(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); // parseJSON(keyOrValue); + return (T) val; + } + catch (Throwable e) { + Log.d(TAG, "getArgValue try {\n" + + " val = Double.valueOf(keyOrValue);" + + "} catch (Throwable e) = " + e.getMessage()); + } + + return (T) current.get(keyOrValue); + } public static class FunctionBean { private String function; + private String schema; private String method; private String[] keys; private Class[] types; @@ -326,7 +813,14 @@ public class AbstractFunctionParser implements FunctionParser { 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 +862,8 @@ public class AbstractFunctionParser implements FunctionParser { * @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(); @@ -388,4 +884,81 @@ public class AbstractFunctionParser implements FunctionParser { } + /** + * 获取JSON对象 + * @param TODO + * @param req + * @param key + * @param clazz + * @return + * @throws Exception + */ + public V getArgVal(@NotNull M req, String key, Class clazz) throws Exception { + // Convert to JSONMap for backward compatibility, replace with proper implementation later + return getArgVal(req, key, clazz, false); + } + + /** + * 获取参数值 + * @param key + * @param clazz 如果有clazz就返回对应的类型,否则返回原始类型 + * @param defaultValue + * @return + * @throws Exception + */ + public V getArgVal(String key, Class clazz, boolean defaultValue) throws Exception { + Object obj = parser != null && JSONMap.isArrayKey(key) ? AbstractParser.getValue(request, key.split("\\,")) : request.get(key); + + if (clazz == null) { + return (V) obj; + } + + // Replace TypeUtils with appropriate casting method + try { + if (obj == null) { + return null; + } + if (clazz.isInstance(obj)) { + return (V) obj; + } + if (clazz == String.class) { + return (V) String.valueOf(obj); + } + if (clazz == Boolean.class || clazz == boolean.class) { + return (V) Boolean.valueOf(String.valueOf(obj)); + } + if (clazz == Integer.class || clazz == int.class) { + return (V) Integer.valueOf(String.valueOf(obj)); + } + if (clazz == Long.class || clazz == long.class) { + return (V) Long.valueOf(String.valueOf(obj)); + } + if (clazz == Double.class || clazz == double.class) { + return (V) Double.valueOf(String.valueOf(obj)); + } + if (clazz == Float.class || clazz == float.class) { + return (V) Float.valueOf(String.valueOf(obj)); + } + if (Map.class.isAssignableFrom(clazz)) { + if (obj instanceof Map) { + return (V) obj; + } + return (V) JSON.parseObject(obj); + } + if (List.class.isAssignableFrom(clazz)) { + if (obj instanceof List) { + return (V) obj; + } + return (V) JSON.parseArray(obj); + } + // Fallback to string conversion + return (V) obj; + } catch (Exception e) { + if (defaultValue) { + return null; + } + throw e; + } + } + } \ No newline at end of file diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index e571cd8b41e9b8b6b191fde8fc1f308585b03d3a..4d56499a067e3392b632c83c4a18ed19044f5592 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -1,63 +1,58 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ package apijson.orm; -import apijson.JSONResponse; -import apijson.Log; -import apijson.NotNull; -import apijson.RequestMethod; -import apijson.StringUtil; +import apijson.*; import apijson.orm.AbstractFunctionParser.FunctionBean; import apijson.orm.exception.ConflictException; +import apijson.orm.exception.CommonException; import apijson.orm.exception.NotExistException; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; +import apijson.orm.exception.UnsupportedDataTypeException; -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.JSON.*; +import static apijson.JSONMap.KEY_COMBINE; +import static apijson.JSONMap.KEY_DROP; +import static apijson.JSONMap.KEY_TRY; +import static apijson.JSONRequest.*; 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, L extends List> + 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 M 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; protected final String arrayTable; - protected final List joinList; + protected final List> joinList; protected final boolean isTable; protected final boolean isArrayMainTable; @@ -68,15 +63,11 @@ public abstract class AbstractObjectParser implements ObjectParser { protected final boolean drop; /**for single object - * @param parentPath - * @param request - * @param name - * @throws Exception */ - public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLConfig arrayConfig + public AbstractObjectParser(@NotNull M request, String parentPath, SQLConfig arrayConfig , boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception { if (request == null) { - throw new IllegalArgumentException(TAG + ".ObjectParser request == null!!!"); + throw new IllegalArgumentException(TAG + ".ObjectParser request == null!!!"); } this.request = request; this.parentPath = parentPath; @@ -88,7 +79,7 @@ public abstract class AbstractObjectParser implements ObjectParser { this.arrayTable = arrayConfig == null ? null : arrayConfig.getTable(); this.joinList = arrayConfig == null ? null : arrayConfig.getJoinList(); - this.isTable = isTable; // apijson.JSONObject.isTableKey(table); + this.isTable = isTable; // apijson.JSONMap.isTableKey(table); this.isArrayMainTable = isArrayMainTable; // isSubquery == false && this.isTable && this.type == SQLConfig.TYPE_ITEM_CHILD_0 && RequestMethod.isGetMethod(method, true); // this.isReuse = isReuse; // isArrayMainTable && arrayConfig != null && arrayConfig.getPosition() > 0; @@ -101,14 +92,19 @@ public abstract class AbstractObjectParser implements ObjectParser { this.drop = false; } else { - this.tri = request.getBooleanValue(KEY_TRY); - this.drop = request.getBooleanValue(KEY_DROP); + this.tri = getBooleanValue(request, KEY_TRY); + this.drop = getBooleanValue(request, KEY_DROP); request.remove(KEY_TRY); request.remove(KEY_DROP); } - } + if (isTable) { + String raw = getString(request, JSONMap.KEY_RAW); + String[] rks = StringUtil.split(raw); + rawKeyList = rks == null || rks.length <= 0 ? null : Arrays.asList(rks); + } + } @Override public String getParentPath() { @@ -116,16 +112,28 @@ public abstract class AbstractObjectParser implements ObjectParser { } @Override - public AbstractObjectParser setParentPath(String parentPath) { + public AbstractObjectParser setParentPath(String parentPath) { this.parentPath = parentPath; return this; } + protected M cache; + @Override + public M getCache() { + return cache; + } + + @Override + public AbstractObjectParser setCache(M cache) { + this.cache = cache; + return this; + } + protected int position; public int getPosition() { return position; } - public AbstractObjectParser setPosition(int position) { + public AbstractObjectParser setPosition(int position) { this.position = position; return this; } @@ -153,9 +161,9 @@ public abstract class AbstractObjectParser implements ObjectParser { protected boolean isReuse; protected String path; - protected JSONObject response; - protected JSONObject sqlRequest; - protected JSONObject sqlReponse; + protected M response; + protected M sqlRequest; + protected M sqlResponse; /** * 自定义关键词 */ @@ -171,17 +179,19 @@ public abstract class AbstractObjectParser implements ObjectParser { /** * 子对象 */ - protected Map childMap; + protected Map childMap; private int objectCount; private int arrayCount; + + private List rawKeyList; /**解析成员 * response重新赋值 * @return null or this * @throws Exception */ @Override - public AbstractObjectParser parse(String name, boolean isReuse) throws Exception { + public AbstractObjectParser parse(String name, boolean isReuse) throws Exception { if (isInvalidate() == false) { this.isReuse = isReuse; this.name = name; @@ -191,137 +201,122 @@ public abstract class AbstractObjectParser implements ObjectParser { this.table = tentry.getKey(); this.alias = tentry.getValue(); - Log.d(TAG, "AbstractObjectParser parentPath = " + parentPath + "; name = " + name + "; table = " + table + "; alias = " + alias); - Log.d(TAG, "AbstractObjectParser type = " + type + "; isTable = " + isTable + "; isArrayMainTable = " + isArrayMainTable); - Log.d(TAG, "AbstractObjectParser isEmpty = " + request.isEmpty() + "; tri = " + tri + "; drop = " + drop); + Log.d(TAG, "AbstractObjectParser parentPath = " + parentPath + "; name = " + name + "; table = " + table + "; alias = " + alias); + Log.d(TAG, "AbstractObjectParser type = " + type + "; isTable = " + isTable + "; isArrayMainTable = " + isArrayMainTable); + Log.d(TAG, "AbstractObjectParser isEmpty = " + request.isEmpty() + "; tri = " + tri + "; drop = " + drop); breakParse = false; - response = new JSONObject(true);//must init - sqlReponse = null;//must init + response = JSON.createJSONObject(); // must init + sqlResponse = null; // must init if (isReuse == false) { - sqlRequest = new JSONObject(true);//must init + sqlRequest = JSON.createJSONObject(); // 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(); + childMap = new LinkedHashMap(); } functionMap = new LinkedHashMap>();//必须执行 - //条件<<<<<<<<<<<<<<<<<<< + // 条件 <<<<<<<<<<<<<<<<<<< List whereList = null; - if (method == PUT) { //这里只有PUTArray需要处理 || method == DELETE) { - String[] combine = StringUtil.split(request.getString(KEY_COMBINE)); + if (method == PUT) { // 这里只有PUTArray需要处理 || method == DELETE) { + String[] combine = StringUtil.split(getString(request, 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); + whereList.add(JSONMap.KEY_ID); + whereList.add(JSONMap.KEY_ID_IN); + // whereList.add(apijson.JSONMap.KEY_USER_ID); + // whereList.add(apijson.JSONMap.KEY_USER_ID_IN); } - //条件>>>>>>>>>>>>>>>>>>> + // 条件>>>>>>>>>>>>>>>>>>> - String key; - Object value; int index = 0; + // hasOtherKeyNotFun = false; + M viceItem = null; for (Entry entry : set) { if (isBreakParse()) { break; } - value = entry.getValue(); + // key 可能为 JSONList,需要进行手动转换(fastjson 为低版本时允许自动转换,如 1.2.21) + // 例如 request json为 "{[]:{"page": 2, "table1":{}}}" + Object field = entry == null ? null : entry.getKey(); + String key = field instanceof Map ? toJSONString(field) : field.toString(); + 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 Map) { + ((Map) obj).put(JSONMap.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 Map)) { if (onParse(key, value) == false) { invalidate(); } } - else if (value instanceof JSONObject) { // JSONObject,往下一级提取 + else if (value instanceof Map) { // JSONRequest,往下一级提取 if (childMap != null) { // 添加到childMap,最后再解析 - childMap.put(key, (JSONObject)value); + childMap.put(key, (M) value); } else { // 直接解析并替换原来的,[]:{} 内必须直接解析,否则会因为丢掉count等属性,并且total@:"/[]/total"必须在[]:{} 后! - response.put(key, onChildParse(index, key, (JSONObject)value)); + Object cache = index <= 0 || type != TYPE_ITEM || viceItem == null ? null : JSON.get(viceItem, key); + Object result = onChildParse(index, key, (M) value, cache); + if (index <= 0 && type == TYPE_ITEM) { + M mainItem = (M) result; + viceItem = result == null ? null : (M) mainItem.remove(AbstractSQLExecutor.KEY_VICE_ITEM); + } + + response.put(key, result); index ++; } } - else if ((method == POST || method == PUT) && value instanceof JSONArray - && JSONRequest.isTableArray(key)) { // JSONArray,批量新增或修改,往下一级提取 - onTableArrayParse(key, (JSONArray) value); + else if ((_method == POST || _method == PUT) && value instanceof List + && JSONMap.isTableArray(key)) { // L,批量新增或修改,往下一级提取 + onTableArrayParse(key, (L) value); } - else if (method == PUT && value instanceof JSONArray && (whereList == null || whereList.contains(key) == false) - && StringUtil.isName(key.replaceFirst("[+-]$", ""))) { // PUT JSONArray - onPUTArrayParse(key, (JSONArray) value); + else if (_method == PUT && value instanceof List && (whereList == null || whereList.contains(key) == false) + && StringUtil.isName(key.replaceFirst("[+-]$", ""))) { // PUT L + onPUTArrayParse(key, (L) value); } - else { // JSONArray或其它Object,直接填充 + else { // L 或其它 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 +325,42 @@ public abstract class AbstractObjectParser implements ObjectParser { } 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(JSONMap.KEY_DATABASE, db); + } + + String ds = parser.getGlobalDatasource(); + if (ds != null) { + sqlRequest.putIfAbsent(JSONMap.KEY_DATASOURCE, ds); } - if (parser.getGlobalSchema() != null && sqlRequest.get(JSONRequest.KEY_SCHEMA) == null) { - sqlRequest.put(JSONRequest.KEY_SCHEMA, parser.getGlobalSchema()); + + String ns = parser.getGlobalNamespace(); + if (ns != null) { + sqlRequest.putIfAbsent(JSONMap.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(JSONMap.KEY_CATALOG, cl); } - if (isSubquery == false) { //解决 SQL 语法报错,子查询不能 EXPLAIN - if (parser.getGlobalExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) { - sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobalExplain()); + String sch = parser.getGlobalSchema(); + if (sch != null) { + sqlRequest.putIfAbsent(JSONMap.KEY_SCHEMA, sch); + } + + if (isSubquery == false) { // 解决 SQL 语法报错,子查询不能 EXPLAIN + Boolean exp = parser.getGlobalExplain(); + if (sch != null) { + sqlRequest.putIfAbsent(JSONMap.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(JSONMap.KEY_CACHE, cache); } } } @@ -368,8 +383,9 @@ public abstract class AbstractObjectParser implements ObjectParser { + //private boolean hasOtherKeyNotFun = false; - /**解析普通成员 + /**解析普通成员 * @param key * @param value * @return whether parse succeed @@ -377,31 +393,46 @@ public abstract class AbstractObjectParser implements ObjectParser { @Override public boolean onParse(@NotNull String key, @NotNull Object value) throws Exception { if (key.endsWith("@")) { // StringUtil.isPath((String) value)) { - // [] 内主表 position > 0 时,用来生成 SQLConfig 的键值对全都忽略,不解析 - if (value instanceof JSONObject) { // key{}@ getRealKey, SQL 子查询对象,JSONObject -> SQLConfig.getSQL + // [] 内主表 position > 0 时,用来生成 SQLConfig 的键值对全都忽略,不解析 + if (value instanceof Map) { // key{}@ getRealKey, SQL 子查询对象,JSONRequest -> SQLConfig.getSQL String replaceKey = key.substring(0, key.length() - 1); - JSONObject subquery = (JSONObject) value; - String range = subquery.getString(JSONRequest.KEY_SUBQUERY_RANGE); - if (range != null && JSONRequest.SUBQUERY_RANGE_ALL.equals(range) == false && JSONRequest.SUBQUERY_RANGE_ANY.equals(range) == false) { - throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ range:value } 中 value 只能为 [" + JSONRequest.SUBQUERY_RANGE_ALL + ", " + JSONRequest.SUBQUERY_RANGE_ANY + "] 中的一个!"); + M subquery = (M) value; + String range = getString(subquery, KEY_SUBQUERY_RANGE); + if (range != null && SUBQUERY_RANGE_ALL.equals(range) == false && SUBQUERY_RANGE_ANY.equals(range) == false) { + throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ range:value } 中 value 只能为 [" + + SUBQUERY_RANGE_ALL + ", " + SUBQUERY_RANGE_ANY + "] 中的一个!"); } + L arr = parser.onArrayParse(subquery, path, key, true, null); - JSONArray arr = parser.onArrayParse(subquery, path, key, true); - - JSONObject obj = arr == null || arr.isEmpty() ? null : arr.getJSONObject(0); + M obj = arr == null || arr.isEmpty() ? null : JSON.get(arr, 0); if (obj == null) { throw new Exception("服务器内部错误,解析子查询 " + path + "/" + key + ":{ } 为 Subquery 对象失败!"); } - String from = subquery.getString(JSONRequest.KEY_SUBQUERY_FROM); - JSONObject arrObj = from == null ? null : obj.getJSONObject(from); + String from = getString(subquery, apijson.JSONRequest.KEY_SUBQUERY_FROM); + boolean isEmpty = StringUtil.isEmpty(from); + M arrObj = isEmpty ? null : JSON.get(obj, 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 Map && JSONMap.isTableKey(k)) { + from = k; + arrObj = (M) 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); + + SQLConfig cfg = (SQLConfig) arrObj.get(AbstractParser.KEY_CONFIG); if (cfg == null) { throw new NotExistException(TAG + ".onParse cfg == null"); } @@ -424,38 +455,55 @@ public abstract class AbstractObjectParser implements ObjectParser { 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 不影响查询,直接跳过 - if (isTable && (key.startsWith("@") == false || JSONRequest.TABLE_KEY_LIST.contains(key))) { + // 非查询关键词 @key 不影响查询,直接跳过 + if (isTable && (key.startsWith("@") == false || JSONMap.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无影响 + + " || apijson.JSONMap.TABLE_KEY_LIST.contains(key)) >> return null;"); + // 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 这个判断现在来看是否还有必要?为啥不允许为 JSONRequest ?以前可能因为防止二次遍历再解析,现在只有一次遍历 +// } +// } +// +// // FIXME 这个判断现在来看是否还有必要?为啥不允许为 JSONRequest ?以前可能因为防止二次遍历再解析,现在只有一次遍历 +// if (targetPath.equals(target)) { // 必须 valuePath 和保证 getValueByPath 传进去的一致! +// Log.d(TAG, "onParse targetPath.equals(target) >>"); +// +// //非查询关键词 @key 不影响查询,直接跳过 +// if (isTable && (key.startsWith("@") == false || apijson.JSONMap.TABLE_KEY_LIST.contains(key))) { +// Log.e(TAG, "onParse isTable && (key.startsWith(@) == false" +// + " || apijson.JSONMap.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 +523,12 @@ public abstract class AbstractObjectParser implements ObjectParser { 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 +536,10 @@ public abstract class AbstractObjectParser implements ObjectParser { 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) { @@ -502,8 +550,8 @@ public abstract class AbstractObjectParser implements ObjectParser { functionMap.put(type, map); } } - else if (isTable && key.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(key) == false) { - customMap.put(key, value); + else if (isTable && key.startsWith("@") && JSONMap.TABLE_KEY_LIST.contains(key) == false) { + customMap.put(key, value); } else { sqlRequest.put(key, value); @@ -515,21 +563,22 @@ public abstract class AbstractObjectParser implements ObjectParser { /** + * @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 Object onChildParse(int index, String key, M value, Object cache) throws Exception { boolean isFirst = index <= 0; boolean isMain = isFirst && type == TYPE_ITEM; - JSON child; + Object child; boolean isEmpty; - if (apijson.JSONObject.isArrayKey(key)) {//APIJSON Array + if (JSONMap.isArrayKey(key)) { // APIJSON Array if (isMain) { throw new IllegalArgumentException(parentPath + "/" + key + ":{} 不合法!" + "数组 []:{} 中第一个 key:{} 必须是主表 TableKey:{} !不能为 arrayKey[]:{} !"); @@ -539,21 +588,40 @@ public abstract class AbstractObjectParser implements ObjectParser { 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); - isEmpty = child == null || ((JSONArray) child).isEmpty(); + String query = getString(value, KEY_QUERY); + child = parser.onArrayParse(value, path, key, isSubquery, cache instanceof List ? (L) cache : null); + isEmpty = child == null || ((List) 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()); + boolean isTableKey = JSONMap.isTableKey(Pair.parseEntry(key, true).getKey()); if (type == TYPE_ITEM && isTableKey == false) { throw new IllegalArgumentException(parentPath + "/" + key + ":{} 不合法!" + "数组 []:{} 中每个 key:{} 都必须是表 TableKey:{} 或 数组 arrayKey[]:{} !"); } - if ( //避免使用 "test":{"Test":{}} 绕过限制,实现查询爆炸 isTableKey && + if ( //避免使用 "test":{"Test":{}} 绕过限制,实现查询爆炸 isTableKey && (arrayConfig == null || arrayConfig.getPosition() == 0)) { objectCount ++; int maxObjectCount = parser.getMaxObjectCount(); @@ -563,28 +631,29 @@ public abstract class AbstractObjectParser implements ObjectParser { } } - 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 Map ? (M) cache : null); - isEmpty = child == null || ((JSONObject) child).isEmpty(); + isEmpty = child == null || ((Map) child).isEmpty(); if (isFirst && isEmpty) { invalidate(); } } // Log.i(TAG, "onChildParse ObjectParser.onParse key = " + key + "; child = " + child); - return isEmpty ? null : child;//只添加! isChildEmpty的值,可能数据库返回数据不够count + return isEmpty ? null : child; // 只添加! isChildEmpty的值,可能数据库返回数据不够count } + //TODO 改用 MySQL json_add,json_remove,json_contains 等函数!不过就没有具体报错了,或许可以新增功能符,或者直接调 SQL 函数 - //TODO 改用 MySQL json_add,json_remove,json_contains 等函数! /**PUT key:[] * @param key * @param array * @throws Exception */ @Override - public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throws Exception { + public void onPUTArrayParse(@NotNull String key, @NotNull L array) throws Exception { if (isTable == false || array.isEmpty()) { sqlRequest.put(key, array); Log.e(TAG, "onPUTArrayParse isTable == false || array == null || array.isEmpty() >> return;"); @@ -600,114 +669,275 @@ public abstract class AbstractObjectParser implements ObjectParser { sqlRequest.put(key, array); return; } - String realKey = AbstractSQLConfig.getRealKey(method, key, false, false); + String realKey = AbstractSQLConfig.gainRealKey(method, key, false, false); //GET > add all 或 remove all > PUT > remove key //GET <<<<<<<<<<<<<<<<<<<<<<<<< - JSONObject rq = new JSONObject(); - 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); + M rq = JSON.createJSONObject(); + rq.put(JSONMap.KEY_ID, request.get(JSONMap.KEY_ID)); + rq.put(JSONMap.KEY_COLUMN, realKey); + M rp = parseResponse(RequestMethod.GET, table, null, rq, null, false); //GET >>>>>>>>>>>>>>>>>>>>>>>>> //add all 或 remove all <<<<<<<<<<<<<<<<<<<<<<<<< - JSONArray targetArray = rp == null ? null : rp.getJSONArray(realKey); - if (targetArray == null) { - targetArray = new JSONArray(); + Object target = rp == null ? null : rp.get(realKey); + if (target instanceof String) { + try { + target = JSON.parse(target); + } catch (Throwable e) { + if (Log.DEBUG) { + Log.e(TAG, "try {\n" + + "\t\t\t\ttarget = parseJSON((String) target);\n" + + "\t\t\t}\n" + + "\t\t\tcatch (Throwable e) = " + e.getMessage()); + } + } + } + + if (apijson.JSON.isBoolOrNumOrStr(target)) { + throw new NullPointerException("PUT " + path + ", " + realKey + " 类型为 " + target.getClass().getSimpleName() + "," + + "不支持 Boolean, String, Number 等类型字段使用 'key+': [] 或 'key-': [] !" + + "对应字段在数据库的值必须为 L, JSONRequest 中的一种!" + + "值为 JSONRequest 类型时传参必须是 '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() + ",不支持这样移除!") + + "对应字段在数据库的值必须为 L, JSONRequest 中的一种,且 key- 移除时,本身的值不能为 null!" + + "值为 JSONRequest 类型时传参必须是 'key+': [{'key': value, 'key2': value2}] 或 'key-': ['key', 'key2'] !" + ); + } + + targetArray = JSON.createJSONArray(); } - 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 + " 必须为 JSONRequest {} !"); + } + 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)); // FIXME, SerializerFeature.WriteMapNullValue)); //PUT >>>>>>>>>>>>>>>>>>>>>>>>> } @Override - public void onTableArrayParse(String key, JSONArray value) throws Exception { - String childKey = key.substring(0, key.length() - JSONRequest.KEY_ARRAY.length()); - JSONArray valueArray = (JSONArray) value; + public void onTableArrayParse(String key, L valueArray) throws Exception { + String childKey = key.substring(0, key.length() - JSONMap.KEY_ARRAY.length()); int allCount = 0; - JSONArray ids = new JSONArray(); + L ids = JSON.createJSONArray(); 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(); + L failedIds = allowPartialFailed ? JSON.createJSONArray() : null; + + int firstFailIndex = -1; + M firstFailReq = null; + Throwable firstFailThrow = null; for (int i = 0; i < valueArray.size(); i++) { //只要有一条失败,则抛出异常,全部失败 //TODO 改成一条多 VALUES 的 SQL 性能更高,报错也更会更好处理,更人性化 - JSONObject item; + M item; try { - item = valueArray.getJSONObject(i); + item = JSON.get(valueArray, i); + if (item == null) { + throw new NullPointerException(); + } } catch (Exception e) { - throw new UnsupportedDataTypeException("批量新增/修改失败!" + key + "/" + i + ":value 中value不合法!类型必须是 OBJECT ,结构为 {} !"); + 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)))); - } - - allCount += count; - ids.add(result.get(idKey)); + Object id = item.get(idKey); + M req = JSON.createJSONObject(childKey, item); + + M result = null; + try { + if (isNeedVerifyContent) { + req = parser.parseCorrectRequest(method, childKey, version, "", req, maxUpdateCount, parser); + } + //parser.getMaxSQLCount() ? 可能恶意调用接口,把数据库拖死 + result = (M) onChildParse(0, "" + i, req, null); + } + catch (Exception e) { + if (allowPartialFailed == false) { + throw e; + } + + if (firstFailThrow == null) { + firstFailThrow = e; + firstFailReq = JSON.get(valueArray, i); // item + } + } + + result = result == null ? null : JSON.get(result, childKey); + + boolean success = JSONResponse.isSuccess(result); + int count = result == null ? 0 : getIntValue(result, 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" : getString(result, 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())); + } + + M allResult = getParser().newSuccessResult(); + if (failedCount > 0) { + allResult.put("failedCount", failedCount); + allResult.put("failedIdList", failedIds); + + M failObj = JSON.createJSONObject(); + failObj.put("index", firstFailIndex); + failObj.put(childKey, firstFailReq); + + if (firstFailThrow instanceof CommonException && firstFailThrow.getCause() != null) { + firstFailThrow = firstFailThrow.getCause(); + } + M obj = firstFailThrow == null ? failObj : getParser().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 M parseResponse(RequestMethod method, String table, String alias + , M request, List> joinList, boolean isProcedure) throws Exception { + SQLConfig config = newSQLConfig(method, table, alias, request, joinList, isProcedure) + .setParser(getParser()) + .setObjectParser(this); return parseResponse(config, isProcedure); } @Override - public JSONObject parseResponse(SQLConfig config, boolean isProcedure) throws Exception { + public M parseResponse(SQLConfig config, boolean isProcedure) throws Exception { + parser = getParser(); if (parser.getSQLExecutor() == null) { parser.createSQLExecutor(); } + if (config.gainParser() == 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); + public SQLConfig newSQLConfig(boolean isProcedure) throws Exception { + String raw = Log.DEBUG == false || sqlRequest == null ? null : getString(sqlRequest, JSONMap.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(JSONMap.KEY_RAW, msg); + } + break; + } + } + + return newSQLConfig(method, table, alias, sqlRequest, joinList, isProcedure) + .setParser(parser) + .setObjectParser(this); } /**SQL 配置,for single object @@ -715,12 +945,12 @@ public abstract class AbstractObjectParser implements ObjectParser { * @throws Exception */ @Override - public AbstractObjectParser setSQLConfig() throws Exception { + public AbstractObjectParser setSQLConfig() throws Exception { return setSQLConfig(RequestMethod.isQueryMethod(method) ? 1 : 0, 0, 0); } @Override - public AbstractObjectParser setSQLConfig(int count, int page, int position) throws Exception { + public AbstractObjectParser setSQLConfig(int count, int page, int position) throws Exception { if (isTable == false || isReuse) { return setPosition(position); } @@ -729,9 +959,11 @@ public abstract class AbstractObjectParser implements ObjectParser { 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); @@ -744,44 +976,47 @@ public abstract class AbstractObjectParser implements ObjectParser { - protected SQLConfig sqlConfig = null;//array item复用 + protected SQLConfig sqlConfig = null;//array item复用 /**SQL查询,for array item - * @param count - * @param page - * @param position * @return this * @throws Exception */ @Override - public AbstractObjectParser executeSQL() throws Exception { + 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 = JSON.createJSONObject(); + sqlResponse.putAll(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; } /** @@ -789,13 +1024,13 @@ public abstract class AbstractObjectParser implements ObjectParser { * @throws Exception */ @Override - public JSONObject response() throws Exception { - if (sqlReponse == null || sqlReponse.isEmpty()) { + public M response() throws Exception { + if (sqlResponse == null || sqlResponse.isEmpty()) { if (isTable) {//Table自身都获取不到值,则里面的Child都无意义,不需要再解析 return null; // response; } } else { - response.putAll(sqlReponse); + response.putAll(sqlResponse); } @@ -824,48 +1059,72 @@ public abstract class AbstractObjectParser implements ObjectParser { //解析函数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); + M 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, JSONRequest currentObject) throws Exception { + // parseFunction(key, value, parentPath, currentName, currentObject, false); + //} + public void parseFunction(String rawKey, String key, String value, String parentPath + , String currentName, M 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); + 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.gainRealKey(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 public void onChildResponse() throws Exception { //把isTable时取出去child解析后重新添加回来 - Set> set = childMap == null ? null : childMap.entrySet(); + Set> set = childMap == null ? null : childMap.entrySet(); if (set != null) { int index = 0; - for (Entry entry : set) { - Object child = entry == null ? null : onChildParse(index, entry.getKey(), entry.getValue()); + for (Entry entry : set) { + 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()) + || (child instanceof Map && ((M) child).isEmpty()) + || (child instanceof List && ((L) child).isEmpty()) ) { continue; } @@ -885,59 +1144,65 @@ public abstract class AbstractObjectParser implements ObjectParser { @SuppressWarnings("unchecked") @Override - public JSONObject onSQLExecute() throws Exception { + public M onSQLExecute() throws Exception { int position = getPosition(); - JSONObject result; - if (isArrayMainTable && position > 0) { // 数组主表使用专门的缓存数据 + M 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) { + parser.putQueryResult(arrayPath, rawList); // 从数组外部引用该数组内值需要 + } else { + 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 已经处理过 + M 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) { // FIXME 改为从缓存获取,而不是 result 查 + result.put(AbstractSQLExecutor.KEY_RAW_LIST, rawList); + } + } + } return result; } @@ -976,7 +1241,7 @@ public abstract class AbstractObjectParser implements ObjectParser { request = null; response = null; sqlRequest = null; - sqlReponse = null; + sqlResponse = null; functionMap = null; customMap = null; @@ -985,16 +1250,13 @@ public abstract class AbstractObjectParser implements ObjectParser { - - - protected RequestMethod method; @Override - public AbstractObjectParser setMethod(RequestMethod method) { + public AbstractObjectParser setMethod(RequestMethod method) { if (this.method != method) { this.method = method; sqlConfig = null; - //TODO ? sqlReponse = null; + //TODO ? sqlResponse = null; } return this; } @@ -1004,8 +1266,6 @@ public abstract class AbstractObjectParser implements ObjectParser { } - - @Override public boolean isTable() { return isTable; @@ -1022,28 +1282,28 @@ public abstract class AbstractObjectParser implements ObjectParser { public String getAlias() { return alias; } + @Override - public SQLConfig getArrayConfig() { + public SQLConfig getArrayConfig() { return arrayConfig; } - @Override - public SQLConfig getSQLConfig() { + public SQLConfig getSQLConfig() { return sqlConfig; } @Override - public JSONObject getResponse() { + public M getResponse() { return response; } @Override - public JSONObject getSqlRequest() { + public M getSQLRequest() { return sqlRequest; } @Override - public JSONObject getSqlReponse() { - return sqlReponse; + public M getSQLResponse() { + return sqlResponse; } @Override @@ -1055,9 +1315,8 @@ public abstract class AbstractObjectParser implements ObjectParser { return functionMap; } @Override - public Map getChildMap() { + public Map getChildMap() { return childMap; } - } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 5135a7279383d84a1423e2c18cecd3f4d8e297e9..cd0b2c7b8afe17ef72115bda72fd21921ea394d0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1,58 +1,49 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ package apijson.orm; -import static apijson.JSONObject.KEY_EXPLAIN; -import static apijson.RequestMethod.GET; +import apijson.*; +import apijson.orm.exception.ConflictException; import java.io.UnsupportedEncodingException; import java.lang.management.ManagementFactory; import java.net.InetAddress; +import java.net.URLDecoder; import java.net.URLEncoder; 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; -import apijson.JSONResponse; -import apijson.Log; -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; -/**parser for parsing request to JSONObject +import apijson.orm.exception.CommonException; +import apijson.orm.exception.UnsupportedDataTypeException; + +import static apijson.JSON.*; +import static apijson.JSONMap.*; +import static apijson.JSONRequest.*; +import static apijson.RequestMethod.CRUD; +import static apijson.RequestMethod.GET; + +/**Parser for parsing request to JSONRequest * @author Lemon */ -public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { +public abstract class AbstractParser, L extends List> + implements Parser { protected static final String TAG = "AbstractParser"; + /** + * JSON 对象、数组对应的数据源、版本、角色、method等 + */ + protected Map> keyObjectAttributesMap = new HashMap<>(); /** * 可以通过切换该变量来控制是否打印关键的接口请求内容。保守起见,该值默认为false。 * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求内容。 @@ -71,25 +62,42 @@ public abstract class AbstractParser implements Parser, Par */ public static boolean IS_PRINT_REQUEST_ENDTIME_LOG = false; + /** + * 可以通过切换该变量来控制返回 trace:stack 字段,如果是 gson 则不设置为 false,避免序列化报错。 + * 与 {@link Log#DEBUG} 任何一个为 true 返回 trace:stack 字段。 + */ + public static boolean IS_RETURN_STACK_TRACE = true; + - 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; } @@ -114,7 +122,6 @@ public abstract class AbstractParser implements Parser, Par return MAX_QUERY_DEPTH; } - /** * method = null */ @@ -122,13 +129,16 @@ public abstract class AbstractParser implements Parser, Par this(null); } /**needVerify = true - * @param requestMethod null ? requestMethod = GET + * @param method null ? requestMethod = GET */ public AbstractParser(RequestMethod method) { - this(method, true); + 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,16 +146,67 @@ public abstract class AbstractParser implements Parser, Par setMethod(method); setNeedVerify(needVerify); } - + protected boolean isRoot = true; public boolean isRoot() { return isRoot; } - public AbstractParser setRoot(boolean isRoot) { + 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; @@ -169,7 +230,7 @@ public abstract class AbstractParser implements Parser, Par return visitor; } @Override - public AbstractParser setVisitor(@NotNull Visitor visitor) { + public AbstractParser setVisitor(@NotNull Visitor visitor) { this.visitor = visitor; return this; } @@ -182,7 +243,7 @@ public abstract class AbstractParser implements Parser, Par } @NotNull @Override - public AbstractParser setMethod(RequestMethod method) { + public AbstractParser setMethod(RequestMethod method) { this.requestMethod = method == null ? GET : method; this.transactionIsolation = RequestMethod.isQueryMethod(method) ? Connection.TRANSACTION_NONE : Connection.TRANSACTION_REPEATABLE_READ; return this; @@ -194,7 +255,7 @@ public abstract class AbstractParser implements Parser, Par return version; } @Override - public AbstractParser setVersion(int version) { + public AbstractParser setVersion(int version) { this.version = version; return this; } @@ -205,7 +266,7 @@ public abstract class AbstractParser implements Parser, Par return tag; } @Override - public AbstractParser setTag(String tag) { + public AbstractParser setTag(String tag) { this.tag = tag; return this; } @@ -214,24 +275,24 @@ public abstract class AbstractParser implements Parser, Par public String getRequestURL() { return requestURL; } - public AbstractParser setRequestURL(String requestURL) { + public AbstractParser setRequestURL(String requestURL) { this.requestURL = requestURL; return this; } - protected JSONObject requestObject; + protected M requestObject; @Override - public JSONObject getRequest() { + public M getRequest() { return requestObject; } @Override - public AbstractParser setRequest(JSONObject request) { + public AbstractParser setRequest(M request) { this.requestObject = request; return this; } protected Boolean globalFormat; - public AbstractParser setGlobalFormat(Boolean globalFormat) { + public AbstractParser setGlobalFormat(Boolean globalFormat) { this.globalFormat = globalFormat; return this; } @@ -240,7 +301,7 @@ public abstract class AbstractParser implements Parser, Par return globalFormat; } protected String globalRole; - public AbstractParser setGlobalRole(String globalRole) { + public AbstractParser setGlobalRole(String globalRole) { this.globalRole = globalRole; return this; } @@ -249,7 +310,7 @@ public abstract class AbstractParser implements Parser, Par return globalRole; } protected String globalDatabase; - public AbstractParser setGlobalDatabase(String globalDatabase) { + public AbstractParser setGlobalDatabase(String globalDatabase) { this.globalDatabase = globalDatabase; return this; } @@ -257,27 +318,49 @@ public abstract class AbstractParser implements Parser, Par 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() { return globalDatasource; } - public AbstractParser setGlobalDatasource(String globalDatasource) { + public AbstractParser setGlobalDatasource(String globalDatasource) { this.globalDatasource = 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) { + public AbstractParser setGlobalExplain(Boolean globalExplain) { this.globalExplain = globalExplain; return this; } @@ -286,7 +369,7 @@ public abstract class AbstractParser implements Parser, Par return globalExplain; } protected String globalCache; - public AbstractParser setGlobalCache(String globalCache) { + public AbstractParser setGlobalCache(String globalCache) { this.globalCache = globalCache; return this; } @@ -296,7 +379,7 @@ public abstract class AbstractParser implements Parser, Par } @Override - public AbstractParser setNeedVerify(boolean needVerify) { + public AbstractParser setNeedVerify(boolean needVerify) { setNeedVerifyLogin(needVerify); setNeedVerifyRole(needVerify); setNeedVerifyContent(needVerify); @@ -309,7 +392,7 @@ public abstract class AbstractParser implements Parser, Par return needVerifyLogin; } @Override - public AbstractParser setNeedVerifyLogin(boolean needVerifyLogin) { + public AbstractParser setNeedVerifyLogin(boolean needVerifyLogin) { this.needVerifyLogin = needVerifyLogin; return this; } @@ -319,7 +402,7 @@ public abstract class AbstractParser implements Parser, Par return needVerifyRole; } @Override - public AbstractParser setNeedVerifyRole(boolean needVerifyRole) { + public AbstractParser setNeedVerifyRole(boolean needVerifyRole) { this.needVerifyRole = needVerifyRole; return this; } @@ -329,34 +412,47 @@ public abstract class AbstractParser implements Parser, Par return needVerifyContent; } @Override - public AbstractParser setNeedVerifyContent(boolean needVerifyContent) { + public AbstractParser setNeedVerifyContent(boolean needVerifyContent) { this.needVerifyContent = needVerifyContent; return this; } - - - - - protected SQLExecutor sqlExecutor; - protected Verifier verifier; + protected SQLExecutor sqlExecutor; + protected Verifier verifier; protected Map queryResultMap;//path-result @Override - public SQLExecutor getSQLExecutor() { + public SQLExecutor getSQLExecutor() { if (sqlExecutor == null) { sqlExecutor = createSQLExecutor(); } + sqlExecutor.setParser(this); return sqlExecutor; } @Override - public Verifier getVerifier() { + public Verifier getVerifier() { if (verifier == null) { verifier = createVerifier().setVisitor(getVisitor()); } + verifier.setParser(this); return verifier; } + /**解析请求JSONObject + * @param request => URLDecoder.decode(request, UTF_8); + * @return + * @throws Exception + */ + public static > M parseRequest(String request) throws Exception { + try { + M req = JSON.parseObject(request); + Objects.requireNonNull(req); + return req; + } catch (Throwable e) { + throw new UnsupportedEncodingException("JSON格式不合法!" + e.getMessage() + "! " + request); + } + } + /**解析请求json并获取对应结果 * @param request * @return @@ -371,7 +467,7 @@ public abstract class AbstractParser implements Parser, Par */ @NotNull @Override - public String parse(JSONObject request) { + public String parse(M request) { return JSON.toJSONString(parseResponse(request)); } @@ -381,12 +477,15 @@ public abstract class AbstractParser implements Parser, Par */ @NotNull @Override - public JSONObject parseResponse(String request) { + public M parseResponse(String request) { Log.d(TAG, "\n\n\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n" + requestMethod + "/parseResponse request = \n" + request + "\n\n"); try { - requestObject = parseRequest(request); + requestObject = JSON.parseObject(request); + if (requestObject == null) { + throw new UnsupportedEncodingException("JSON格式不合法!"); + } } catch (Exception e) { return newErrorResult(e, isRoot); } @@ -403,12 +502,30 @@ public abstract class AbstractParser implements Parser, Par */ @NotNull @Override - public JSONObject parseResponse(JSONObject request) { + public M parseResponse(M request) { long startTime = System.currentTimeMillis(); Log.d(TAG, "parseResponse startTime = " + startTime + "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n\n "); requestObject = request; + try { + setGlobalFormat(getBoolean(requestObject, KEY_FORMAT)); + requestObject.remove(KEY_FORMAT); + } catch (Exception e) { + return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot); + } + + try { + setVersion(getIntValue(requestObject, KEY_VERSION)); + requestObject.remove(KEY_VERSION); + + if (getMethod() != RequestMethod.CRUD) { + setTag(getString(requestObject, KEY_TAG)); + requestObject.remove(KEY_TAG); + } + } catch (Exception e) { + return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot); + } verifier = createVerifier().setVisitor(getVisitor()); @@ -428,44 +545,47 @@ public abstract class AbstractParser implements Parser, Par //必须在parseCorrectRequest后面,因为parseCorrectRequest可能会添加 @role if (isNeedVerifyRole() && globalRole == null) { try { - setGlobalRole(requestObject.getString(JSONRequest.KEY_ROLE)); - requestObject.remove(JSONRequest.KEY_ROLE); + setGlobalRole(getString(requestObject, KEY_ROLE)); + requestObject.remove(KEY_ROLE); } catch (Exception e) { return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot); } } 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)); - setGlobalExplain(requestObject.getBoolean(JSONRequest.KEY_EXPLAIN)); - setGlobalCache(requestObject.getString(JSONRequest.KEY_CACHE)); - - requestObject.remove(JSONRequest.KEY_FORMAT); - requestObject.remove(JSONRequest.KEY_DATABASE); - requestObject.remove(JSONRequest.KEY_SCHEMA); - requestObject.remove(JSONRequest.KEY_DATASOURCE); - requestObject.remove(JSONRequest.KEY_EXPLAIN); - requestObject.remove(JSONRequest.KEY_CACHE); + setGlobalDatabase(getString(requestObject, KEY_DATABASE)); + setGlobalDatasource(getString(requestObject, KEY_DATASOURCE)); + setGlobalNamespace(getString(requestObject, KEY_NAMESPACE)); + setGlobalCatalog(getString(requestObject, KEY_CATALOG)); + setGlobalSchema(getString(requestObject, KEY_SCHEMA)); + + setGlobalExplain(getBoolean(requestObject, KEY_EXPLAIN)); + setGlobalCache(getString(requestObject, KEY_CACHE)); + + requestObject.remove(KEY_DATABASE); + requestObject.remove(KEY_DATASOURCE); + requestObject.remove(KEY_NAMESPACE); + requestObject.remove(KEY_CATALOG); + requestObject.remove(KEY_SCHEMA); + + requestObject.remove(KEY_EXPLAIN); + requestObject.remove(KEY_CACHE); } catch (Exception e) { return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot); } final String requestString = JSON.toJSONString(request);//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 +596,57 @@ public abstract class AbstractParser implements Parser, Par 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); + + // FIXME 暂时先直接移除,后续排查是在哪里 put 进来 + requestObject.remove(KEY_DATABASE); + requestObject.remove(KEY_DATASOURCE); + requestObject.remove(KEY_NAMESPACE); + requestObject.remove(KEY_CATALOG); + requestObject.remove(KEY_SCHEMA); - JSONObject res = (globalFormat != null && globalFormat) && JSONResponse.isSuccess(requestObject) ? new JSONResponse(requestObject) : requestObject; + M res = (globalFormat != null && globalFormat) && JSONResponse.isSuccess(requestObject) ? JSONResponse.format(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()); + sqlExecutor = getSQLExecutor(); + res.put("sql:generate|cache|execute|maxExecute", sqlExecutor.getGeneratedSQLCount() + "|" + sqlExecutor.getCachedSQLCount() + "|" + sqlExecutor.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()); + + if (IS_RETURN_STACK_TRACE) { + L list = JSON.createJSONArray(); + + StackTraceElement[] traces = t.getStackTrace(); + if (traces != null) { // && traces.length > 0) { + for (StackTraceElement trace : traces) { + list.add(trace == null ? null : trace.toString()); + } + } + + res.put("trace:stack", list); + } } } 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"); @@ -511,6 +658,7 @@ public abstract class AbstractParser implements Parser, Par Log.fd(TAG, requestMethod + "/parseResponse endTime = " + endTime + "; duration = " + duration); Log.sl("", '>', "\n\n\n"); } + return res; } @@ -529,7 +677,7 @@ public abstract class AbstractParser implements Parser, Par * @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)); } @@ -548,90 +696,54 @@ public abstract class AbstractParser implements Parser, Par } - /**解析请求JSONObject - * @param request => URLDecoder.decode(request, UTF_8); - * @return - * @throws Exception - */ - @NotNull - public static JSONObject parseRequest(String request) throws Exception { - JSONObject obj = JSON.parseObject(request); - if (obj == null) { - throw new UnsupportedEncodingException("JSON格式不合法!"); - } - return obj; - } - @Override - public JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, String name, @NotNull JSONObject request - , int maxUpdateCount, SQLCreator creator) throws Exception { + public M parseCorrectRequest(RequestMethod method, String tag, int version, String name, @NotNull M request + , int maxUpdateCount, SQLCreator creator) throws Exception { if (RequestMethod.isPublicMethod(method)) { 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 * @return */ - public static JSONObject wrapRequest(RequestMethod method, String tag, JSONObject object, boolean isStructure) { + public M wrapRequest(RequestMethod method, String tag, M object, boolean isStructure) { boolean putTag = ! isStructure; if (object == null || object.containsKey(tag)) { //tag 是 Table 名或 Table[] if (putTag) { if (object == null) { - object = new JSONObject(true); + object = JSON.createJSONObject(); } - object.put(JSONRequest.KEY_TAG, tag); + object.put(KEY_TAG, tag); } return object; } boolean isDiffArrayKey = tag.endsWith(":[]"); - boolean isArrayKey = isDiffArrayKey || JSONRequest.isArrayKey(tag); + boolean isArrayKey = isDiffArrayKey || isArrayKey(tag); String key = isArrayKey ? tag.substring(0, tag.length() - (isDiffArrayKey ? 3 : 2)) : tag; - JSONObject target = object; - if (apijson.JSONObject.isTableKey(key)) { + M target = object; + if (isTableKey(key)) { if (isDiffArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对为 { "Comment[]":[], "TYPE": { "Comment[]": "OBJECT[]" } ... } if (isStructure && (method == RequestMethod.POST || method == RequestMethod.PUT)) { String arrKey = key + "[]"; if (target.containsKey(arrKey) == false) { - target.put(arrKey, new JSONArray()); + target.put(arrKey, JSON.createJSONArray()); } try { - JSONObject type = target.getJSONObject(Operation.TYPE.name()); + Map type = JSON.get(target, Operation.TYPE.name()); if (type == null || (type.containsKey(arrKey) == false)) { if (type == null) { - type = new JSONObject(true); + type = new LinkedHashMap(); } type.put(arrKey, "OBJECT[]"); @@ -639,24 +751,24 @@ public abstract class AbstractParser implements Parser, Par } } catch (Throwable e) { - Log.w(TAG, "wrapRequest try { JSONObject type = target.getJSONObject(Operation.TYPE.name()); } catch (Exception e) = " + e.getMessage()); + Log.w(TAG, "wrapRequest try { Map type = target.getJSONObject(Operation.TYPE.name()); } catch (Exception e) = " + e.getMessage()); } } } else { //自动为 tag = Comment 的 { ... } 包一层为 { "Comment": { ... } } if (isArrayKey == false || RequestMethod.isGetMethod(method, true)) { - target = new JSONObject(true); + target = JSON.createJSONObject(); target.put(tag, object); } else if (target.containsKey(key) == false) { - target = new JSONObject(true); + target = JSON.createJSONObject(); target.put(key, object); } } } if (putTag) { - target.put(JSONRequest.KEY_TAG, tag); + target.put(KEY_TAG, tag); } return target; @@ -668,104 +780,142 @@ public abstract class AbstractParser implements Parser, Par * @param msg * @return */ - public static JSONObject newResult(int code, String msg) { - return newResult(code, msg, false); + public M newResult(int code, String msg) { + return newResult(code, msg, null); } - /**新建带状态内容的JSONObject + + /** + * 添加JSONObject的状态内容,一般用于错误提示结果 + * + * @param code + * @param msg + * @param warn + * @return + */ + public M 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 M 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 M extendResult(M 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); + object = JSON.createJSONObject(); } - - 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); } - String m = StringUtil.getString(object.getString(JSONResponse.KEY_MSG)); + String m = StringUtil.get(getString(object, JSONResponse.KEY_MSG)); if (m.isEmpty() == false) { - msg = m + " ;\n " + StringUtil.getString(msg); + msg = m + " ;\n " + StringUtil.get(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) { + public M extendSuccessResult(M object) { return extendSuccessResult(object, false); } + + public M extendSuccessResult(M 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 M extendSuccessResult(M object, String warn, boolean isRoot) { + return extendResult(object, JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, warn, isRoot); } + /**获取请求成功的状态内容 * @return */ - public static JSONObject newSuccessResult() { - return newSuccessResult(false); + public M newSuccessResult() { + return newSuccessResult(null); } + /**获取请求成功的状态内容 + * @param warn + * @return + */ + public M 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 M 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 M extendErrorResult(M object, Throwable e) { return extendErrorResult(object, e, false); } /**添加请求成功的状态内容 @@ -774,90 +924,95 @@ public abstract class AbstractParser implements Parser, Par * @param isRoot * @return */ - public static JSONObject extendErrorResult(JSONObject object, Exception e, boolean isRoot) { + public M extendErrorResult(M 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 M extendErrorResult(M 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 * @return */ - public static JSONObject newErrorResult(Exception e) { + public M newErrorResult(Exception e) { return newErrorResult(e, false); } /**新建错误状态内容 @@ -865,73 +1020,28 @@ public abstract class AbstractParser implements Parser, Par * @param isRoot * @return */ - public static JSONObject newErrorResult(Exception e, boolean isRoot) { + public M 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); + int 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); + public M parseCorrectRequest() throws Exception { return parseCorrectRequest(requestMethod, tag, version, "", requestObject, getMaxUpdateCount(), this); } @@ -945,19 +1055,18 @@ public abstract class AbstractParser implements Parser, Par * @throws Exception */ @Override - public JSONObject getStructure(@NotNull String table, String method, String tag, int version) throws Exception { - // TODO 目前只使用 Request 而不使用 Response,所以这里写死用 REQUEST_MAP,以后可能 Response 表也会与 Request 表合并,用字段来区分 + public M getStructure(@NotNull String table, String method, String tag, int version) throws Exception { String cacheKey = AbstractVerifier.getCacheKeyForRequest(method, tag); - SortedMap versionedMap = AbstractVerifier.REQUEST_MAP.get(cacheKey); + SortedMap> versionedMap = (SortedMap>) AbstractVerifier.REQUEST_MAP.get(cacheKey); - JSONObject result = versionedMap == null ? null : versionedMap.get(Integer.valueOf(version)); + Map result = versionedMap == null ? null : versionedMap.get(Integer.valueOf(version)); if (result == null) { // version <= 0 时使用最新,version > 0 时使用 > version 的最接近版本(最小版本) - Set> set = versionedMap == null ? null : versionedMap.entrySet(); + Set>> set = versionedMap == null ? null : versionedMap.entrySet(); if (set != null && set.isEmpty() == false) { - Entry maxEntry = null; + Entry> maxEntry = null; - for (Entry entry : set) { + for (Entry> entry : set) { if (entry == null || entry.getKey() == null || entry.getValue() == null) { continue; } @@ -994,23 +1103,24 @@ public abstract class AbstractParser implements Parser, Par return null; // 已使用 REQUEST_MAP 缓存全部,但没查到 } - //获取指定的JSON结构 <<<<<<<<<<<<<< - SQLConfig config = createSQLConfig().setMethod(GET).setTable(table); + // 获取指定的JSON结构 <<<<<<<<<<<<<< + SQLConfig config = createSQLConfig().setMethod(GET).setTable(table); + config.setParser(this); config.setPrepared(false); config.setColumn(Arrays.asList("structure")); Map where = new HashMap(); where.put("method", method); - where.put(JSONRequest.KEY_TAG, tag); + where.put(KEY_TAG, tag); if (version > 0) { - where.put(JSONRequest.KEY_VERSION + ">=", version); + where.put(KEY_VERSION + ">=", version); } config.setWhere(where); - config.setOrder(JSONRequest.KEY_VERSION + (version > 0 ? "+" : "-")); + config.setOrder(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,所以要么启动时一次性缓存全部后面只用缓存,要么每次都查数据库 @@ -1018,25 +1128,27 @@ public abstract class AbstractParser implements Parser, Par // AbstractVerifier.REQUEST_MAP.put(cacheKey, versionedMap); } - return getJSONObject(result, "structure"); //解决返回值套了一层 "structure":{} + return JSON.get(result, "structure"); //解决返回值套了一层 "structure":{} } - protected Map arrayObjectParserCacheMap = new HashMap<>(); + 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 M onObjectParse(final M request, String parentPath, String name + , final SQLConfig arrayConfig, boolean isSubquery, M cache) throws Exception { if (Log.DEBUG) { Log.i(TAG, "\ngetObject: parentPath = " + parentPath @@ -1065,11 +1177,11 @@ public abstract class AbstractParser implements Parser, Par String table = entry.getKey(); //Comment // String alias = entry.getValue(); //to - boolean isTable = apijson.JSONObject.isTableKey(table); + boolean isTable = isTableKey(table); boolean isArrayMainTable = isSubquery == false && isTable && type == SQLConfig.TYPE_ITEM_CHILD_0 && arrayConfig != null && RequestMethod.isGetMethod(arrayConfig.getMethod(), true); boolean isReuse = isArrayMainTable && position > 0; - ObjectParser op = null; + ObjectParser op = null; if (isReuse) { // 数组主表使用专门的缓存数据 op = arrayObjectParserCacheMap.get(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2)); op.setParentPath(parentPath); @@ -1078,9 +1190,13 @@ public abstract class AbstractParser implements Parser, Par 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; + M response = null; if (op != null) {//SQL查询结果为空时,functionMap和customMap没有意义 if (arrayConfig == null) { //Common @@ -1090,16 +1206,16 @@ public abstract class AbstractParser implements Parser, Par int query = arrayConfig.getQuery(); //total 这里不能用arrayConfig.getType(),因为在createObjectParser.onChildParse传到onObjectParse时已被改掉 - if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != JSONRequest.QUERY_TABLE && position == 0) { + if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != apijson.JSONRequest.QUERY_TABLE && position == 0) { //TODO 应在这里判断 @column 中是否有聚合函数,而不是 AbstractSQLConfig.getColumnString - - JSONObject rp; + + Map 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 +1223,7 @@ public abstract class AbstractParser implements Parser, Par subqy.setFrom(cfg.getTable()); subqy.setConfig(cfg); - SQLConfig countSQLCfg = createSQLConfig(); + SQLConfig countSQLCfg = createSQLConfig(); countSQLCfg.setColumn(Arrays.asList("count(*):count")); countSQLCfg.setFrom(subqy); @@ -1118,14 +1234,14 @@ public abstract class AbstractParser implements Parser, Par 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); } if (rp != null) { int index = parentPath.lastIndexOf("]/"); if (index >= 0) { - int total = rp.getIntValue(JSONResponse.KEY_COUNT); + int total = getIntValue(rp, JSONResponse.KEY_COUNT); String pathPrefix = parentPath.substring(0, index) + "]/"; putQueryResult(pathPrefix + JSONResponse.KEY_TOTAL, total); @@ -1137,24 +1253,29 @@ public abstract class AbstractParser implements Parser, Par if (max < 0) { max = 0; } + int min = getMinQueryPage(); + + page += min; + max += min; - JSONObject pagination = new JSONObject(true); + M pagination = JSON.createJSONObject(); Object explain = rp.get(JSONResponse.KEY_EXPLAIN); - if (explain instanceof JSONObject) { + if (explain instanceof Map) { 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(apijson.JSONRequest.KEY_COUNT, count); + pagination.put(apijson.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) { - query = JSONRequest.QUERY_TOTAL;//数量不够了,不再往后查询 + if (total <= count*(page - min)) { + query = apijson.JSONRequest.QUERY_TOTAL;//数量不够了,不再往后查询 } } } @@ -1163,7 +1284,7 @@ public abstract class AbstractParser implements Parser, Par } //Table - if (query == JSONRequest.QUERY_TOTAL) { + if (query == apijson.JSONRequest.QUERY_TOTAL) { response = null;//不再往后查询 } else { response = op @@ -1189,21 +1310,24 @@ public abstract class AbstractParser implements Parser, Par } /**获取对象数组,该对象数组处于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 L onArrayParse(M request, String parentPath, String name, boolean isSubquery, L 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(KEY_METHOD) == null ? requestMethod : RequestMethod.valueOf(getString(request, 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 条件 @@ -1213,39 +1337,41 @@ public abstract class AbstractParser implements Parser, Par //不能改变,因为后面可能继续用到,导致1以上都改变 []:{0:{Comment[]:{0:{Comment:{}},1:{...},...}},1:{...},...} - final String query = request.getString(JSONRequest.KEY_QUERY); - final Boolean compat = request.getBoolean(JSONRequest.KEY_COMPAT); - final Integer count = request.getInteger(JSONRequest.KEY_COUNT); //TODO 如果不想用默认数量可以改成 getIntValue(JSONRequest.KEY_COUNT); - final Integer page = request.getInteger(JSONRequest.KEY_PAGE); - final Object join = request.get(JSONRequest.KEY_JOIN); + final String query = getString(request, apijson.JSONRequest.KEY_QUERY); + final Boolean compat = getBoolean(request, apijson.JSONRequest.KEY_COMPAT); + final Integer count = getInteger(request, apijson.JSONRequest.KEY_COUNT); //TODO 如果不想用默认数量可以改成 getIntValue(apijson.JSONRequest.KEY_COUNT); + final Integer page = getInteger(request, apijson.JSONRequest.KEY_PAGE); + final Object join = request.get(apijson.JSONRequest.KEY_JOIN); int query2; if (query == null) { - query2 = JSONRequest.QUERY_TABLE; + query2 = apijson.JSONRequest.QUERY_TABLE; } else { switch (query) { case "0": - case JSONRequest.QUERY_TABLE_STRING: - query2 = JSONRequest.QUERY_TABLE; + case apijson.JSONRequest.QUERY_TABLE_STRING: + query2 = apijson.JSONRequest.QUERY_TABLE; break; case "1": - case JSONRequest.QUERY_TOTAL_STRING: - query2 = JSONRequest.QUERY_TOTAL; + case apijson.JSONRequest.QUERY_TOTAL_STRING: + query2 = apijson.JSONRequest.QUERY_TOTAL; break; case "2": - case JSONRequest.QUERY_ALL_STRING: - query2 = JSONRequest.QUERY_ALL; + case apijson.JSONRequest.QUERY_ALL_STRING: + query2 = apijson.JSONRequest.QUERY_ALL; break; default: - throw new IllegalArgumentException(path + "/" + JSONRequest.KEY_QUERY + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [TABLE, TOTAL, ALL] 内 !"); + throw new IllegalArgumentException(path + "/" + apijson.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 + "/" + apijson.JSONRequest.KEY_PAGE + ":value 中 value 的值不合法!必须在 " + minPage + "-" + maxPage + " 内 !"); } //不用total限制数量了,只用中断机制,total只在query = 1,2的时候才获取 @@ -1253,14 +1379,14 @@ public abstract class AbstractParser implements Parser, Par int max = isSubquery ? count2 : getMaxQueryCount(); if (count2 < 0 || count2 > max) { - throw new IllegalArgumentException(path + "/" + JSONRequest.KEY_COUNT + ":value 中 value 的值不合法!必须在 0-" + max + " 内 !"); + throw new IllegalArgumentException(path + "/" + apijson.JSONRequest.KEY_COUNT + ":value 中 value 的值不合法!必须在 0-" + max + " 内 !"); } - request.remove(JSONRequest.KEY_QUERY); - request.remove(JSONRequest.KEY_COMPAT); - request.remove(JSONRequest.KEY_COUNT); - request.remove(JSONRequest.KEY_PAGE); - request.remove(JSONRequest.KEY_JOIN); + request.remove(apijson.JSONRequest.KEY_QUERY); + request.remove(apijson.JSONRequest.KEY_COMPAT); + request.remove(apijson.JSONRequest.KEY_COUNT); + request.remove(apijson.JSONRequest.KEY_PAGE); + request.remove(apijson.JSONRequest.KEY_JOIN); Log.d(TAG, "onArrayParse query = " + query + "; count = " + count + "; page = " + page + "; join = " + join); if (request.isEmpty()) { // 如果条件成立,说明所有的 parentPath/name:request 中request都无效!!! 后续都不执行,没必要还原数组关键词浪费性能 @@ -1268,7 +1394,7 @@ public abstract class AbstractParser implements Parser, Par return null; } - JSONArray response = null; + L response = null; try { int size = count2 == 0 ? max : count2; //count为每页数量,size为第page页实际数量,max(size) = count Log.d(TAG, "onArrayParse size = " + size + "; page = " + page2); @@ -1283,30 +1409,32 @@ public abstract class AbstractParser implements Parser, Par String[] childKeys = StringUtil.split(childPath, "-", false); if (childKeys == null || childKeys.length <= 0 || request.containsKey(childKeys[0]) == false) { childKeys = null; - } - else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // 可能无需提取,直接返回 rawList 即可 + } + else if (childKeys.length == 1 && 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; + Map parent; boolean isExtract = true; + response = JSON.createJSONArray(); //生成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; } @@ -1314,18 +1442,18 @@ public abstract class AbstractParser implements Parser, Par long startTime = System.currentTimeMillis(); /* 这里优化了 Table[]: { Table:{} } 这种情况下的性能 - * 如果把 List 改成 JSONArray 来减少以下 addAll 一次复制,则会导致 AbstractSQLExecutor 等其它很多地方 get 要改为 getJSONObject, - * 修改类型会导致不兼容旧版依赖 ORM 的项目,而且整体上性能只有特殊情况下性能提升,其它非特殊情况下因为多出很多 instanceof JSONObject 的判断而降低了性能。 + * 如果把 List> 改成 L 来减少以下 addAll 一次复制,则会导致 AbstractSQLExecutor 等其它很多地方 get 要改为 getJSONObject, + * 修改类型会导致不兼容旧版依赖 ORM 的项目,而且整体上性能只有特殊情况下性能提升,其它非特殊情况下因为多出很多 instanceof Map 的判断而降低了性能。 */ - JSONObject fo = i != 0 || arrTableKey == null ? null : parent.getJSONObject(arrTableKey); + Map fo = i != 0 || arrTableKey == null ? null : JSON.get(parent, arrTableKey); @SuppressWarnings("unchecked") - List list = fo == null ? null : (List) fo.remove(AbstractSQLExecutor.KEY_RAW_LIST); + 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 - response.addAll(list); // List cannot match List response = new JSONArray(list); + response.addAll(list); // List> cannot match List response = JSON.createJSONArray(list); long endTime = System.currentTimeMillis(); // 0ms Log.d(TAG, "\n onArrayParse <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n for (int i = 0; i < (isSubquery ? 1 : size); i++) " @@ -1370,11 +1498,11 @@ public abstract class AbstractParser implements Parser, Par } finally { //后面还可能用到,要还原 - request.put(JSONRequest.KEY_QUERY, query); - request.put(JSONRequest.KEY_COMPAT, compat); - request.put(JSONRequest.KEY_COUNT, count); - request.put(JSONRequest.KEY_PAGE, page); - request.put(JSONRequest.KEY_JOIN, join); + request.put(apijson.JSONRequest.KEY_QUERY, query); + request.put(apijson.JSONRequest.KEY_COMPAT, compat); + request.put(apijson.JSONRequest.KEY_COUNT, count); + request.put(apijson.JSONRequest.KEY_PAGE, page); + request.put(apijson.JSONRequest.KEY_JOIN, join); } if (Log.DEBUG) { @@ -1388,67 +1516,106 @@ public abstract class AbstractParser implements Parser, Par private static final List JOIN_COPY_KEY_LIST; static { // TODO 不全 JOIN_COPY_KEY_LIST = new ArrayList(); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ROLE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATABASE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_SCHEMA); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATASOURCE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COLUMN); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_NULL); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_CAST); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COMBINE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_GROUP); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING_AND); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ORDER); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_RAW); - } - + JOIN_COPY_KEY_LIST.add(KEY_ROLE); + JOIN_COPY_KEY_LIST.add(KEY_DATABASE); + JOIN_COPY_KEY_LIST.add(KEY_NAMESPACE); + JOIN_COPY_KEY_LIST.add(KEY_CATALOG); + JOIN_COPY_KEY_LIST.add(KEY_SCHEMA); + JOIN_COPY_KEY_LIST.add(KEY_DATASOURCE); + JOIN_COPY_KEY_LIST.add(KEY_COLUMN); + JOIN_COPY_KEY_LIST.add(KEY_NULL); + JOIN_COPY_KEY_LIST.add(KEY_CAST); + JOIN_COPY_KEY_LIST.add(KEY_COMBINE); + JOIN_COPY_KEY_LIST.add(KEY_GROUP); + JOIN_COPY_KEY_LIST.add(KEY_HAVING); + JOIN_COPY_KEY_LIST.add(KEY_HAVING_AND); + JOIN_COPY_KEY_LIST.add(KEY_SAMPLE); + JOIN_COPY_KEY_LIST.add(KEY_LATEST); + JOIN_COPY_KEY_LIST.add(KEY_PARTITION); + JOIN_COPY_KEY_LIST.add(KEY_FILL); + JOIN_COPY_KEY_LIST.add(KEY_ORDER); + JOIN_COPY_KEY_LIST.add(KEY_KEY); + JOIN_COPY_KEY_LIST.add(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; + private List> onJoinParse(Object join, M request) throws Exception { + Map joinMap = null; - if (join instanceof JSONObject) { - joinMap = (JSONObject) join; + if (join instanceof Map) { + joinMap = (M) join; } else if (join instanceof String) { String[] sArr = request == null || request.isEmpty() ? null : StringUtil.split((String) join); if (sArr != null && sArr.length > 0) { - joinMap = new JSONObject(true); //注意:这里必须要保证join连接顺序,保证后边遍历是按照join参数的顺序生成的SQL + joinMap = new LinkedHashMap(); //注意:这里必须要保证join连接顺序,保证后边遍历是按照join参数的顺序生成的SQL for (int i = 0; i < sArr.length; i++) { - joinMap.put(sArr[i], new JSONObject()); + joinMap.put(sArr[i], new LinkedHashMap()); } } } else if (join != null){ - throw new UnsupportedDataTypeException(TAG + ".onJoinParse join 只能是 String 或 JSONObject 类型!"); + throw new UnsupportedDataTypeException(TAG + ".onJoinParse join 只能是 String 或 Map 类型!"); + } + + List> slashKeys = new ArrayList<>(); + List> nonSlashKeys = new ArrayList<>(); + Set> entries = joinMap == null ? null : joinMap.entrySet(); + + if (entries == null || entries.isEmpty()) { + Log.e(TAG, "onJoinParse set == null || set.isEmpty() >> return null;"); + return null; } + for (Entry e : entries) { + String path = e.getKey(); + if (path != null && path.indexOf("/") > 0) { + slashKeys.add(e); // 以 / 开头的 key,例如 whereJoinMap = new LinkedHashMap<>(); + + for (Entry e : nonSlashKeys) { + String tableKey = e.getKey(); // 如 "Location_info" + Object tableObj = e.getValue(); // value 是 Map + + if (request.containsKey(tableKey)) { + whereJoinMap.put(tableKey, tableObj); + } else { + Log.w(TAG, "跳过 join 中 key = " + tableKey + ",因为它不在 request 中"); + } + } + + + Set> set = joinMap == null ? null : new LinkedHashSet<>(slashKeys); - Set> set = joinMap == null ? null : joinMap.entrySet(); if (set == null || set.isEmpty()) { Log.e(TAG, "onJoinParse set == null || set.isEmpty() >> return null;"); return null; } - List joinList = new ArrayList<>(); + List> joinList = new ArrayList<>(); for (Entry e : set) { // { &/User:{}, == false) { + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":value 中value不合法!" + "必须为 &/Table0/key0, ( ) <> () * @@ -1457,57 +1624,89 @@ public abstract class AbstractParser implements Parser, Par // } path = path.substring(index + 1); - index = path.indexOf("/"); + index = path.lastIndexOf("/"); String tableKey = index < 0 ? path : path.substring(0, index); // User:owner + int index2 = tableKey.lastIndexOf("/"); + String arrKey = index2 < 0 ? null : tableKey.substring(0, index2); + if (arrKey != null && isArrayKey(arrKey) == false) { + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + " 不是合法的数组 key[] !" + + "@ APP JOIN 最多允许跨 1 层,只能是子数组,且数组对象中不能有 join: value 键值对!"); + } + + tableKey = index2 < 0 ? tableKey : tableKey.substring(index2+1); + apijson.orm.Entry entry = Pair.parseEntry(tableKey, true); String table = entry.getKey(); // User if (StringUtil.isName(table) == false) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!" + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!" + "必须为 &/Table0, 格式!" + e2.getMessage()); + } + + if (arrKey != null) { + if (parentPathObj.get(apijson.JSONRequest.KEY_JOIN) != null) { + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + ":{ join: value } 中 value 不合法!" + + "@ APP JOIN 最多允许跨 1 层,只能是子数组,且数组对象中不能有 join: value 键值对!"); + } + + Integer subPage = getInteger(parentPathObj, apijson.JSONRequest.KEY_PAGE); + if (subPage != null && subPage != 0) { + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + ":{ page: value } 中 value 不合法!" + + "@ APP JOIN 最多允许跨 1 层,只能是子数组,且数组对象中 page 值只能为 null 或 0 !"); + } } - - JSONObject refObj = new JSONObject(tableObj.size(), true); + boolean isAppJoin = "@".equals(joinType); + + M refObj = JSON.createJSONObject(); String key = index < 0 ? null : path.substring(index + 1); // id@ if (key != null) { // 指定某个 key 为 JOIN ON 条件 if (key.indexOf("@") != key.length() - 1) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":" + e.getKey() + " 中 " + key + " 不合法!" + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":" + e.getKey() + " 中 " + key + " 不合法!" + "必须为 &/Table0,> tableSet = tableObj.entrySet(); // 取出所有 join 条件 - JSONObject requestObj = new JSONObject(true); // (JSONObject) obj.clone(); + M requestObj = JSON.createJSONObject(); // (Map) obj.clone(); boolean matchSingle = false; for (Entry tableEntry : tableSet) { @@ -1532,7 +1731,19 @@ public abstract class AbstractParser implements Parser, Par apijson.orm.Entry te = tk == null || p.substring(ind2 + 1).indexOf("/") >= 0 ? null : Pair.parseEntry(tk, true); - if (te != null && JSONRequest.isTableKey(te.getKey()) && request.get(tk) instanceof JSONObject) { + if (te != null && isTableKey(te.getKey()) && request.get(tk) instanceof Map) { + if (isAppJoin) { + if (refObj.size() >= 1) { + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":" + e.getKey() + " 中 " + k + " 不合法!" + + "@ APP JOIN 必须有且只有一个引用赋值键值对!"); + } + + if (StringUtil.isName(k.substring(0, k.length() - 1)) == false) { + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 中 " + k + " 不合法 !" + + "@ APP JOIN 只允许 key@:/Table/refKey 这种 = 等价连接!"); + } + } + refObj.put(k, v); continue; } @@ -1544,7 +1755,7 @@ public abstract class AbstractParser implements Parser, Par continue; } - throw new UnsupportedOperationException(table + "/" + k + " 不合法!" + JSONRequest.KEY_JOIN + " 关联的 Table 中," + throw new UnsupportedOperationException(table + "/" + k + " 不合法!" + apijson.JSONRequest.KEY_JOIN + " 关联的 Table 中," + "join: ?/Table/key 时只能有 1 个 key@:value;join: ?/Table 时所有 key@:value 要么是符合 join 格式,要么能直接解析成具体值!"); // TODO 支持 join on } @@ -1555,7 +1766,7 @@ public abstract class AbstractParser implements Parser, Par } else { if (k.endsWith("@")) { - throw new UnsupportedOperationException(table + "/" + k + " 不合法!" + JSONRequest.KEY_JOIN + " 关联的 Table 中," + throw new UnsupportedOperationException(table + "/" + k + " 不合法!" + apijson.JSONRequest.KEY_JOIN + " 关联的 Table 中," + "join: ?/Table/key 时只能有 1 个 key@:value;join: ?/Table 时所有 key@:value 要么是符合 join 格式,要么能直接解析成具体值!"); // TODO 支持 join on } @@ -1567,20 +1778,33 @@ public abstract class AbstractParser implements Parser, Par Set> refSet = refObj.entrySet(); if (refSet.isEmpty() && "*".equals(joinType) == false) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 alias 值 " + alias + " 不合法!" + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":value 中 value 的 alias 值 " + alias + " 不合法!" + "必须为 &/Table0, j = new Join<>(); j.setPath(e.getKey()); j.setJoinType(joinType); j.setTable(table); j.setAlias(alias); - j.setOuter((JSONObject) outer); + + M outerObj = (M) JSON.createJSONObject((Map) outer); + j.setOn(outerObj); j.setRequest(requestObj); + if (whereJoinMap.containsKey(table)) { + Object rawOuter = whereJoinMap.get(table); + M outerObj1 = (M) JSON.createJSONObject((Map) rawOuter); + j.setOuter(outerObj1); + } + + if (arrKey != null) { + Integer count = getInteger(parentPathObj, apijson.JSONRequest.KEY_COUNT); + j.setCount(count == null ? getDefaultQueryCount() : count); + } + List onList = new ArrayList<>(); for (Entry refEntry : refSet) { String originKey = refEntry.getKey(); @@ -1590,7 +1814,7 @@ public abstract class AbstractParser implements Parser, Par throw new IllegalArgumentException(e.getKey() + ":value 中 value 值 " + targetPath + " 不合法!必须为引用赋值的路径 '/targetTable/targetKey' !"); } - // 取出引用赋值路径 targetPath 对应的 Table 和 key + // 取出引用赋值路径 targetPath 对应的 Table 和 key index = targetPath.lastIndexOf("/"); String targetKey = index < 0 ? null : targetPath.substring(index + 1); if (StringUtil.isName(targetKey) == false) { @@ -1613,50 +1837,51 @@ public abstract class AbstractParser implements Parser, Par 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 格式!"); } //对引用的JSONObject添加条件 - JSONObject targetObj; + Map targetObj; try { - targetObj = request.getJSONObject(targetTableKey); + targetObj = JSON.get(request, targetTableKey); } catch (Exception e2) { - throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的 '" + targetTableKey + "':value 中 value 类型不合法!必须是 {} 这种 JSONObject 格式!" + e2.getMessage()); + throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的 '" + targetTableKey + "':value 中 value 类型不合法!必须是 {} 这种 Map 格式!" + e2.getMessage()); } if (targetObj == null) { - throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 JSONObject 格式!"); + throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 Map 格式!"); } - + 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() + " 不合法!必须满足英文单词变量名格式!"); + throw new IllegalArgumentException(apijson.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 +1889,8 @@ public abstract class AbstractParser implements Parser, Par // 保证和 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 +1902,32 @@ public abstract class AbstractParser implements Parser, Par * @param pathKeys * @return */ - protected static Object getValue(JSONObject parent, String[] pathKeys) { - if (parent == null || pathKeys == null || pathKeys.length <= 0) { + public static V getValue(Object parent, String[] pathKeys) { + int len = parent == null || pathKeys == null ? 0 : pathKeys.length; + if (len <= 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; - for (int i = 0; i < last; i++) {//一步一步到达指定位置 - if (parent == null) {//不存在或路径错误(中间的key对应value不是JSONObject) + // 逐层到达child的直接容器JSONObject parent + Object v = parent; + for (int i = 0; i < len; i++) { // 一步一步到达指定位置 + if (v == null) { // 不存在或路径错误(中间的key对应value不是JSONObject) break; } - parent = getJSONObject(parent, pathKeys[i]); + + String k = getDecodedKey(pathKeys[i]); + try { + v = getFromObjOrArr(v, k); + } catch (Throwable e) { + if (IS_PRINT_BIG_LOG) { + e.printStackTrace(); + } + v = null; + } } - return parent == null ? null : parent.get(pathKeys[last]); + return (V) v; } @@ -1717,8 +1952,8 @@ public abstract class AbstractParser implements Parser, Par */ public static String getAbsPath(String path, String name) { Log.i(TAG, "getPath path = " + path + "; name = " + name + " <<<<<<<<<<<<<"); - path = StringUtil.getString(path); - name = StringUtil.getString(name); + path = StringUtil.get(path); + name = StringUtil.get(name); if (StringUtil.isNotEmpty(path, false)) { if (StringUtil.isNotEmpty(name, false)) { path += ((name.startsWith("/") ? "" : "/") + name); @@ -1754,12 +1989,12 @@ public abstract class AbstractParser implements Parser, Par pos = ps[i+1].contains("/") == false ? ps[i+1] : ps[i+1].substring(0, ps[i+1].indexOf("/")); if ( - //StringUtil.isNumer(pos) && + //StringUtil.isNumer(pos) && vs[i+1].startsWith(pos + "/") == false) { vs[i+1] = pos + "/" + vs[i+1]; } } - return StringUtil.getString(vs, "]/"); + return StringUtil.get(vs, "]/"); } } return valuePath; @@ -1798,16 +2033,16 @@ public abstract class AbstractParser implements Parser, Par } //取出key被valuePath包含的result,再从里面获取key对应的value - JSONObject parent = null; + Object parent = null; String[] keys = null; - for (Entry entry : queryResultMap.entrySet()){ + for (Entry entry : queryResultMap.entrySet()){ String path = entry.getKey(); if (valuePath.startsWith(path + "/")) { try { - parent = (JSONObject) entry.getValue(); + parent = entry.getValue(); } catch (Exception e) { - Log.e(TAG, "getValueByPath try { parent = (JSONObject) queryResultMap.get(path); } catch { " - + "\n parent not instanceof JSONObject!"); + Log.e(TAG, "getValueByPath try { parent = (Map) queryResultMap.get(path); } catch { " + + "\n parent not instanceof Map!"); parent = null; } if (parent != null) { @@ -1817,172 +2052,109 @@ public abstract class AbstractParser implements Parser, Par } } - //逐层到达targetKey的直接容器JSONObject parent - if (keys != null && keys.length > 1) { - for (int i = 0; i < keys.length - 1; i++) {//一步一步到达指定位置parentPath - if (parent == null) {//不存在或路径错误(中间的key对应value不是JSONObject) - break; - } - parent = getJSONObject(parent, keys[i]); - } - } - - 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,否则可能绕过安全机制 - if (target != null) { - Log.i(TAG, "getValueByPath >> getValue >> return target = " + target); - return target; - } - } - - - //从requestObject中取值 - target = getValue(requestObject, StringUtil.splitPath(valuePath)); - if (target != null) { - Log.i(TAG, "getValueByPath >> getValue >> return target = " + target); - return target; + target = getValue(parent, keys); // 逐层到达targetKey的直接容器JSONObject parent + if (target == null) { //从requestObject中取值 + target = getValue(requestObject, StringUtil.splitPath(valuePath)); } - Log.i(TAG, "getValueByPath return valuePath;"); - return valuePath; + Log.i(TAG, "getValueByPath >> getValue >> return target = " + target); + return target; } - //依赖引用关系 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - - public static JSONObject getJSONObject(JSONObject object, String key) { + /**解码 引用赋值 路径中的 key,支持把 URL encode 后的值,转为 decode 后的原始值,例如 %2Fuser%2Flist -> /user/list ; %7B%7D -> [] + * @param key + * @return + */ + public static String getDecodedKey(String key) { try { - return object.getJSONObject(key); - } catch (Exception e) { - Log.i(TAG, "getJSONObject try { return object.getJSONObject(key);" - + " } catch (Exception e) { \n" + e.getMessage()); + return URLDecoder.decode(key, StringUtil.UTF_8); + } catch (Throwable e) { + return key; } - return null; } + //依赖引用关系 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + public static final String KEY_CONFIG = "config"; public static final String KEY_SQL = "sql"; - protected Map> arrayMainCacheMap = new HashMap<>(); - public void putArrayMainCache(String arrayPath, List mainTableDataList) { + protected Map> arrayMainCacheMap = new HashMap<>(); + public void putArrayMainCache(String arrayPath, List mainTableDataList) { arrayMainCacheMap.put(arrayPath, mainTableDataList); } - public List getArrayMainCache(String arrayPath) { + public List getArrayMainCache(String arrayPath) { return arrayMainCacheMap.get(arrayPath); } - public JSONObject getArrayMainCacheItem(String arrayPath, int position) { - List list = getArrayMainCache(arrayPath); + public M getArrayMainCacheItem(String arrayPath, int position) { + List list = getArrayMainCache(arrayPath); return list == null || position >= list.size() ? null : list.get(position); } - /**执行 SQL 并返回 JSONObject + /**执行 SQL 并返回 Map * @param config * @return * @throws Exception */ @Override - public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Exception { + public M 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); + M sqlObj = JSON.createJSONObject(); sqlObj.put(KEY_CONFIG, config); return sqlObj;//容易丢失信息 JSON.parseObject(config); } try { - JSONObject result; + M result; boolean explain = config.isExplain(); if (explain) { //如果先执行 explain,则 execute 会死循环,所以只能先执行非 explain config.setExplain(false); //对下面 config.getSQL(false); 生效 - JSONObject res = getSQLExecutor().execute(config, false); + M 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); + Map explainResult = config.isMain() && config.getPosition() != 0 ? null : getSQLExecutor().execute(config, false); if (explainResult == null) { result = res; } else { - result = new JSONObject(true); + result = JSON.createJSONObject(); result.put(KEY_EXPLAIN, explainResult); result.putAll(res); } } else {//如果是更新请求,不执行explain,但可以返回sql - result = new JSONObject(true); - result.put(KEY_SQL, config.getSQL(false)); + result = JSON.createJSONObject(); + result.put(KEY_SQL, config.gainSQL(false)); result.putAll(res); } } else { - sqlExecutor = getSQLExecutor(); - result = sqlExecutor.execute(config, false); - // FIXME 改为直接在 sqlExecutor 内加好,最后 Parser 取结果,可以解决并发执行导致内部计算出错 + result = getSQLExecutor().execute(config, false); + // 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 +2183,7 @@ public abstract class AbstractParser implements Parser, Par @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 +2220,9 @@ public abstract class AbstractParser implements Parser, Par */ 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 +2268,327 @@ public abstract class AbstractParser implements Parser, Par queryResultMap = null; } + private void setOpMethod(Map request, ObjectParser op, String key) { + String _method = key == null ? null : getString(request, KEY_METHOD); + if (_method != null) { + RequestMethod method = RequestMethod.valueOf(_method); // 必须精准匹配,避免缓存命中率低 + this.setMethod(method); + op.setMethod(method); + } + } + + protected M getRequestStructure(RequestMethod method, String tag, int version) throws Exception { + // 获取指定的JSON结构 <<<<<<<<<<<< + M 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; + } + + public static final Map KEY_METHOD_ENUM_MAP; + static { + 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); + } + + protected M batchVerify(RequestMethod method, String tag, int version, String name, @NotNull M request, int maxUpdateCount, SQLCreator creator) throws Exception { + M correctRequest = JSON.createJSONObject(); + 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 + KEY_ARRAY)) { + throw new IllegalArgumentException("对象名重复,请添加别名区分 ! 重复对象名为: " + key); + } + + boolean isPost = KEY_POST.equals(key); + // @post、@get 等 RequestMethod + try { + RequestMethod keyMethod = isPost ? RequestMethod.POST : KEY_METHOD_ENUM_MAP.get(key); + if (keyMethod != null) { + // 如果不匹配,异常不处理即可 + removeTmpKeys.add(key); + + Object val = request.get(key); + Map obj = val instanceof Map ? JSON.get(request, key) : null; + if (obj == null) { + if (val instanceof String) { + String[] tbls = StringUtil.split((String) val); + if (tbls != null && tbls.length > 0) { + obj = new LinkedHashMap(); + 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 && isTableArray(tbl) + ? tbl.substring(0, tbl.length() - 2) + ":[]" : ""); + } + } + } + else { + throw new IllegalArgumentException(key + ": value 中 value 类型错误,只能是 String 或 Map {} !"); + } + } + + 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(KEY_METHOD, keyMethod); + keyObjectAttributesMap.put(objKey, objAttrMap); + + Object objVal = objEntry.getValue(); + Map objAttrJson = objVal instanceof Map ? JSON.getMap(obj, objKey) : null; + if (objAttrJson == null) { + if (objVal instanceof String) { + objAttrMap.put(KEY_TAG, "".equals(objVal) ? objKey : objVal); + } + else { + throw new IllegalArgumentException(key + ": { " + objKey + ": value 中 value 类型错误,只能是 String 或 Map {} !"); + } + } + 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 KEY_DATASOURCE: + case KEY_SCHEMA: + case KEY_DATABASE: + case KEY_VERSION: + case KEY_ROLE: + objAttrMap.put(objAttrKey, entry.getValue()); + break; + case KEY_TAG: + hasTag = true; + objAttrMap.put(objAttrKey, entry.getValue()); + break; + default: + break; + } + } + + if (hasTag == false) { + objAttrMap.put(KEY_TAG, isPost && isTableArray(objKey) + ? objKey.substring(0, objKey.length() - 2) + ":[]" : objKey); + } + } + } + continue; + } + + // 1、非crud,对于没有显式声明操作方法的,直接用 URL(/get, /post 等) 对应的默认操作方法 + // 2、crud, 没有声明就用 GET + // 3、兼容 sql@ Map,设置 GET方法 + // 将method 设置到每个object, op执行会解析 + Object obj = request.get(key); + + if (obj instanceof Map) { + Map attrMap = keyObjectAttributesMap.get(key); + + if (attrMap == null) { + // 数组会解析为对象进行校验,做一下兼容 + if (keyObjectAttributesMap.get(key + KEY_ARRAY) == null) { + if (method == RequestMethod.CRUD || key.endsWith("@")) { + ((Map) obj).put(KEY_METHOD, GET); + Map objAttrMap = new HashMap<>(); + objAttrMap.put(KEY_METHOD, GET); + keyObjectAttributesMap.put(key, objAttrMap); + } else { + ((Map) obj).put(KEY_METHOD, method); + Map objAttrMap = new HashMap<>(); + objAttrMap.put(KEY_METHOD, method); + keyObjectAttributesMap.put(key, objAttrMap); + } + } else { + setRequestAttribute(key, true, KEY_METHOD, request); + setRequestAttribute(key, true, KEY_DATASOURCE, request); + setRequestAttribute(key, true, KEY_SCHEMA, request); + setRequestAttribute(key, true, KEY_DATABASE, request); + setRequestAttribute(key, true, KEY_VERSION, request); + setRequestAttribute(key, true, KEY_ROLE, request); + } + } else { + setRequestAttribute(key, false, KEY_METHOD, request); + setRequestAttribute(key, false, KEY_DATASOURCE, request); + setRequestAttribute(key, false, KEY_SCHEMA, request); + setRequestAttribute(key, false, KEY_DATABASE, request); + setRequestAttribute(key, false, KEY_VERSION, request); + setRequestAttribute(key, false, KEY_ROLE, request); + } + } + + if (key.startsWith("@") || key.endsWith("@")) { + correctRequest.put(key, obj); + continue; + } + + if (obj instanceof Map || obj instanceof List) { + RequestMethod _method; + if (obj instanceof Map) { + Map tblObj = JSON.getMap(request, key); + String mn = tblObj == null ? null : getString(tblObj, KEY_METHOD); + _method = mn == null ? null : RequestMethod.valueOf(mn); + String combine = _method == null ? null : getString(tblObj, 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(KEY_METHOD, GET); + keyObjectAttributesMap.put(key, objAttrMap); + } else { + _method = method; + Map objAttrMap = new HashMap<>(); + objAttrMap.put(KEY_METHOD, method); + keyObjectAttributesMap.put(key, objAttrMap); + } + } else { + _method = (RequestMethod) attrMap.get(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(":")) { + M object = getRequestStructure(_method, tag, version); + M ret = objectVerify(_method, tag, version, name, request, maxUpdateCount, creator, object); + correctRequest.putAll(ret); + break; + } + + String _tag = buildTag(request, key, method, tag); + M object = getRequestStructure(_method, _tag, version); + if (method == RequestMethod.CRUD && StringUtil.isEmpty(tag, true)) { + M requestItem = JSON.createJSONObject(); + requestItem.put(key, obj); + Map 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 Map request) { + Map attrMap = keyObjectAttributesMap.get(isArray ? key + KEY_ARRAY : key); + Object attrVal = attrMap == null ? null : attrMap.get(attrKey); + Map obj = attrVal == null ? null : JSON.get(request, key); + + if (obj != null && obj.get(attrKey) == null) { + // 如果对象内部已经包含该属性,不覆盖 + obj.put(attrKey, attrVal); + } + } + + protected String buildTag(Map request, String key, RequestMethod method, String tag) { + if (method == RequestMethod.CRUD) { + Map attrMap = keyObjectAttributesMap.get(key); + Object _tag = attrMap == null ? null : attrMap.get(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 M objectVerify(RequestMethod method, String tag, int version, String name, @NotNull M request + , int maxUpdateCount, SQLCreator creator, M object) throws Exception { + // 获取指定的JSON结构 >>>>>>>>>>>>>> + M target = wrapRequest(method, tag, object, true); + // Map clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} + return getVerifier().setParser(this).verifyRequest(method, name, target, request, maxUpdateCount, getGlobalDatabase(), getGlobalSchema()); + } + + /*** + * 兼容url crud, 获取真实method + * @param method = crud + * @param key + * @return + */ + public RequestMethod getRealMethod(RequestMethod method, String key, Object value) { + if (method == CRUD && (value instanceof Map || value instanceof List)) { + Map attrMap = keyObjectAttributesMap.get(key); + Object _method = attrMap == null ? null : attrMap.get(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 c9c9c86233eb73ec9d64eaf0f27fae71593bdcb0..d9e9eb01628746315f13b2b159e399406fd3d24e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1,83 +1,31 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ 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 apijson.*; +import apijson.orm.Join.On; +import apijson.orm.exception.NotExistException; +import apijson.orm.exception.UnsupportedDataTypeException; +import apijson.orm.model.*; + +import java.util.*; 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 apijson.JSON; -import apijson.JSONResponse; -import apijson.Log; -import apijson.NotNull; -import apijson.RequestMethod; -import apijson.SQL; -import apijson.StringUtil; -import apijson.orm.Join.On; -import apijson.orm.exception.NotExistException; -import apijson.orm.model.Access; -import apijson.orm.model.Column; -import apijson.orm.model.Document; -import apijson.orm.model.ExtendedProperty; -import apijson.orm.model.Function; -import apijson.orm.model.PgAttribute; -import apijson.orm.model.PgClass; -import apijson.orm.model.Request; -import apijson.orm.model.SysColumn; -import apijson.orm.model.SysTable; -import apijson.orm.model.Table; -import apijson.orm.model.TestRecord; +import static apijson.JSON.getBoolean; +import static apijson.JSON.getString; +import static apijson.JSONMap.*; +import static apijson.RequestMethod.*; +import static apijson.SQL.*; /**config sql for JSON Request * @author Lemon */ -public abstract class AbstractSQLConfig implements SQLConfig { +public abstract class AbstractSQLConfig, L extends List> + implements SQLConfig { private static final String TAG = "AbstractSQLConfig"; /** @@ -91,25 +39,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 +104,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 +130,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 +151,35 @@ 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_QUESTDB); + DATABASE_LIST.add(DATABASE_IOTDB); + DATABASE_LIST.add(DATABASE_SNOWFLAKE); + DATABASE_LIST.add(DATABASE_DATABEND); + 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); + DATABASE_LIST.add(DATABASE_DORIS); RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 @@ -166,6 +198,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 +255,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 +270,387 @@ 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("PRECEDING", ""); // 往前 + 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 +663,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 +681,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 +689,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 gainParser() { + 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 gainObjectParser() { + 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[] gainDBVersionNums() { + if (dbVersionNums == null || dbVersionNums.length <= 0) { + dbVersionNums = SQLConfig.super.gainDBVersionNums(); + } + 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 @@ -745,7 +890,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { return KEY_USER_ID; } - private RequestMethod method; //操作方法 private boolean prepared = true; //预编译 private boolean main = true; @@ -761,6 +905,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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; //表名 @@ -768,10 +914,16 @@ public abstract class AbstractSQLConfig implements SQLConfig { private String group; //分组方式的字符串数组,','分隔 private String havingCombine; //聚合函数的字符串数组,','分隔 private Map having; //聚合函数的字符串数组,','分隔 + private String sample; //取样方式的字符串数组,','分隔 + private String latest; //最近方式的字符串数组,','分隔 + private String partition; //分区方式的字符串数组,','分隔 + private String fill; //填充方式的字符串数组,','分隔 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; //子查询临时表 + private Subquery from; //子查询临时表 private List column; //表内字段名(或函数名,仅查询操作可用)的字符串数组,','分隔 private List> values; //对应表内字段的值的字符串数组,','分隔 private List nulls; @@ -785,19 +937,19 @@ public abstract class AbstractSQLConfig implements SQLConfig { private int count; //Table数量 private int page; //Table所在页码 private int position; //Table在[]中的位置 - private int query; //JSONRequest.query - private Boolean compat; //JSONRequest.compat query total + private int query; //apijson.JSONRequest.QUERY + private Boolean compat; //apijson.JSONMap.compat query total private int type; //ObjectParser.type private int cache; private boolean explain; - private List joinList; //连表 配置列表 + private List> joinList; //连表 配置列表 //array item >>>>>>>>>> private boolean test; //测试 private String procedure; - public SQLConfig setProcedure(String procedure) { + public AbstractSQLConfig setProcedure(String procedure) { this.procedure = procedure; return this; } @@ -827,16 +979,16 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 +997,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return main; } @Override - public AbstractSQLConfig setMain(boolean main) { + public AbstractSQLConfig setMain(boolean main) { this.main = main; return this; } @@ -856,7 +1008,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return id; } @Override - public AbstractSQLConfig setId(Object id) { + public AbstractSQLConfig setId(Object id) { this.id = id; return this; } @@ -866,7 +1018,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return idIn; } @Override - public AbstractSQLConfig setIdIn(Object idIn) { + public AbstractSQLConfig setIdIn(Object idIn) { this.idIn = idIn; return this; } @@ -877,7 +1029,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return userId; } @Override - public AbstractSQLConfig setUserId(Object userId) { + public AbstractSQLConfig setUserId(Object userId) { this.userId = userId; return this; } @@ -887,7 +1039,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return userIdIn; } @Override - public AbstractSQLConfig setUserIdIn(Object userIdIn) { + public AbstractSQLConfig setUserIdIn(Object userIdIn) { this.userIdIn = userIdIn; return this; } @@ -898,7 +1050,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return role; } @Override - public AbstractSQLConfig setRole(String role) { + public AbstractSQLConfig setRole(String role) { this.role = role; return this; } @@ -908,7 +1060,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return distinct; } @Override - public SQLConfig setDistinct(boolean distinct) { + public AbstractSQLConfig setDistinct(boolean distinct) { this.distinct = distinct; return this; } @@ -918,7 +1070,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return database; } @Override - public SQLConfig setDatabase(String database) { + public AbstractSQLConfig setDatabase(String database) { this.database = database; return this; } @@ -926,78 +1078,358 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @return db == null ? DEFAULT_DATABASE : db */ @NotNull - public String getSQLDatabase() { + public String gainSQLDatabase() { String db = getDatabase(); return db == null ? DEFAULT_DATABASE : db; // "" 表示已设置,不需要用全局默认的 StringUtil.isEmpty(db, false)) { } + @Override + public boolean isTSQL() { // 兼容 TSQL 语法 + return isOracle() || isSQLServer() || isDb2(); + } + @Override + public boolean isMSQL() { // 兼容 MySQL 语法,但不一定可以使用它的 JDBC/ODBC + return isMySQL() || isTiDB() || isMariaDB() || isSQLite() || isTDengine(); + } + @Override + public boolean isPSQL() { // 兼容 PostgreSQL 语法,但不一定可以使用它的 JDBC/ODBC + return isPostgreSQL() || isCockroachDB() || isOpenGauss() || isInfluxDB() || isTimescaleDB() || isQuestDB() || isDuckDB(); + } + @Override public boolean isMySQL() { - return isMySQL(getSQLDatabase()); + return isMySQL(gainSQLDatabase()); } public static boolean isMySQL(String db) { return DATABASE_MYSQL.equals(db); } + @Override public boolean isPostgreSQL() { - return isPostgreSQL(getSQLDatabase()); + return isPostgreSQL(gainSQLDatabase()); } public static boolean isPostgreSQL(String db) { return DATABASE_POSTGRESQL.equals(db); } + @Override public boolean isSQLServer() { - return isSQLServer(getSQLDatabase()); + return isSQLServer(gainSQLDatabase()); } public static boolean isSQLServer(String db) { return DATABASE_SQLSERVER.equals(db); } + @Override public boolean isOracle() { - return isOracle(getSQLDatabase()); + return isOracle(gainSQLDatabase()); } public static boolean isOracle(String db) { return DATABASE_ORACLE.equals(db); } + @Override public boolean isDb2() { - return isDb2(getSQLDatabase()); + return isDb2(gainSQLDatabase()); } public static boolean isDb2(String db) { return DATABASE_DB2.equals(db); } + + @Override + public boolean isMariaDB() { + return isMariaDB(gainSQLDatabase()); + } + public static boolean isMariaDB(String db) { + return DATABASE_MARIADB.equals(db); + } + + @Override + public boolean isTiDB() { + return isTiDB(gainSQLDatabase()); + } + public static boolean isTiDB(String db) { + return DATABASE_TIDB.equals(db); + } + + @Override + public boolean isCockroachDB() { + return isCockroachDB(gainSQLDatabase()); + } + public static boolean isCockroachDB(String db) { + return DATABASE_COCKROACHDB.equals(db); + } + + @Override + public boolean isDameng() { + return isDameng(gainSQLDatabase()); + } + public static boolean isDameng(String db) { + return DATABASE_DAMENG.equals(db); + } + + @Override + public boolean isKingBase() { + return isKingBase(gainSQLDatabase()); + } + public static boolean isKingBase(String db) { + return DATABASE_KINGBASE.equals(db); + } + + @Override + public boolean isElasticsearch() { + return isElasticsearch(gainSQLDatabase()); + } + public static boolean isElasticsearch(String db) { + return DATABASE_ELASTICSEARCH.equals(db); + } + + @Override + public boolean isManticore() { + return isManticore(gainSQLDatabase()); + } + public static boolean isManticore(String db) { + return DATABASE_MANTICORE.equals(db); + } + @Override public boolean isClickHouse() { - return isClickHouse(getSQLDatabase()); + return isClickHouse(gainSQLDatabase()); } public static boolean isClickHouse(String db) { return DATABASE_CLICKHOUSE.equals(db); } + @Override public boolean isHive() { - return isHive(getSQLDatabase()); + return isHive(gainSQLDatabase()); } public static boolean isHive(String db) { return DATABASE_HIVE.equals(db); } @Override - public String getQuote() { - return isMySQL()||isClickHouse() ? "`" : "\""; + public boolean isPresto() { + return isPresto(gainSQLDatabase()); + } + public static boolean isPresto(String db) { + return DATABASE_PRESTO.equals(db); } @Override - public String getSchema() { - return schema; + public boolean isTrino() { + return isTrino(gainSQLDatabase()); } - /** - * @param sqlTable - * @return - */ + public static boolean isTrino(String db) { + return DATABASE_TRINO.equals(db); + } + + @Override + public boolean isSnowflake() { + return isSnowflake(gainSQLDatabase()); + } + public static boolean isSnowflake(String db) { + return DATABASE_SNOWFLAKE.equals(db); + } + + @Override + public boolean isDatabend() { + return isDatabend(gainSQLDatabase()); + } + public static boolean isDatabend(String db) { + return DATABASE_DATABEND.equals(db); + } + + @Override + public boolean isDatabricks() { + return isDatabricks(gainSQLDatabase()); + } + public static boolean isDatabricks(String db) { + return DATABASE_DATABRICKS.equals(db); + } + + @Override + public boolean isCassandra() { + return isCassandra(gainSQLDatabase()); + } + public static boolean isCassandra(String db) { + return DATABASE_CASSANDRA.equals(db); + } + + @Override + public boolean isMilvus() { + return isMilvus(gainSQLDatabase()); + } + public static boolean isMilvus(String db) { + return DATABASE_MILVUS.equals(db); + } + + @Override + public boolean isInfluxDB() { + return isInfluxDB(gainSQLDatabase()); + } + public static boolean isInfluxDB(String db) { + return DATABASE_INFLUXDB.equals(db); + } + + @Override + public boolean isTDengine() { + return isTDengine(gainSQLDatabase()); + } + public static boolean isTDengine(String db) { + return DATABASE_TDENGINE.equals(db); + } + + @Override + public boolean isTimescaleDB() { + return isTimescaleDB(gainSQLDatabase()); + } + public static boolean isTimescaleDB(String db) { + return DATABASE_TIMESCALEDB.equals(db); + } + + @Override + public boolean isQuestDB() { + return isQuestDB(gainSQLDatabase()); + } + public static boolean isQuestDB(String db) { + return DATABASE_QUESTDB.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(gainSQLDatabase()); + } + public static boolean isRedis(String db) { + return DATABASE_REDIS.equals(db); + } + + @Override + public boolean isMongoDB() { + return isMongoDB(gainSQLDatabase()); + } + public static boolean isMongoDB(String db) { + return DATABASE_MONGODB.equals(db); + } + + @Override + public boolean isKafka() { + return isKafka(gainSQLDatabase()); + } + public static boolean isKafka(String db) { + return DATABASE_KAFKA.equals(db); + } + + @Override + public boolean isMQ() { + return isMQ(gainSQLDatabase()); + } + public static boolean isMQ(String db) { + return DATABASE_MQ.equals(db) || isKafka(db); + } + + @Override + public boolean isSQLite() { + return isSQLite(gainSQLDatabase()); + } + public static boolean isSQLite(String db) { + return DATABASE_SQLITE.equals(db); + } + + @Override + public boolean isDuckDB() { + return isDuckDB(gainSQLDatabase()); + } + public static boolean isDuckDB(String db) { + return DATABASE_DUCKDB.equals(db); + } + + @Override + public boolean isSurrealDB() { + return isSurrealDB(gainSQLDatabase()); + } + public static boolean isSurrealDB(String db) { + return DATABASE_SURREALDB.equals(db); + } + + @Override + public boolean isOpenGauss() { + return isOpenGauss(gainSQLDatabase()); + } + public static boolean isOpenGauss(String db) { + return DATABASE_OPENGAUSS.equals(db); + } + + @Override + public boolean isDoris() { + return isDoris(gainSQLDatabase()); + } + public static boolean isDoris(String db) { + return DATABASE_DORIS.equals(db); + } + + @Override + public String getQuote() { // MongoDB 同时支持 `tbl` 反引号 和 "col" 双引号 + if(isElasticsearch() || isManticore() || isIoTDB() || isSurrealDB()) { + return ""; + } + return isMySQL() || isMariaDB() || isTiDB() || isClickHouse() || isTDengine() || isMilvus() || isDoris() ? "`" : "\""; + } + + 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 gainSQLCatalog() { + 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 - public String getSQLSchema() { + @Override + public String gainSQLSchema() { 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 +1439,27 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 String getSchema() { + return schema; + } + @Override - public AbstractSQLConfig setSchema(String schema) { + 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,14 +1470,14 @@ public abstract class AbstractSQLConfig implements SQLConfig { return datasource; } @Override - public SQLConfig setDatasource(String datasource) { + public AbstractSQLConfig setDatasource(String datasource) { this.datasource = datasource; return this; } /**请求传进来的Table名 * @return - * @see {@link #getSQLTable()} + * @see {@link #gainSQLTable()} */ @Override public String getTable() { @@ -1046,312 +1487,572 @@ public abstract class AbstractSQLConfig implements SQLConfig { * 通过 {@link #TABLE_KEY_MAP} 映射 * @return */ - @JSONField(serialize = false) @Override - public String getSQLTable() { - // String t = TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; - //如果要强制小写,则可在子类重写这个方法再 toLowerCase return DATABASE_POSTGRESQL.equals(getDatabase()) ? t.toLowerCase() : t; - return TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; + public String gainSQLTable() { + // 如果要强制小写,则可在子类重写这个方法再 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() { + public String gainTablePath() { String q = getQuote(); - String sch = getSQLSchema(); - String sqlTable = getSQLTable(); + String ns = isSurrealDB() ? getSQLNamespace() : null; + String cl = isPSQL() ? gainSQLCatalog() : null; + String sch = gainSQLSchema(); + String sqlTable = gainSQLTable(); + + 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() ? gainAs() + q + gainSQLAlias() + q : ""); + } + @Override + public AbstractSQLConfig setTable(String table) { //Table已经在Parser中校验,所以这里不用防SQL注入 + this.table = table; + return this; + } + + public String gainAs() { + return isOracle() || isManticore() ? " " : " AS "; + } + + @Override + public String getAlias() { + return alias; + } + @Override + public AbstractSQLConfig setAlias(String alias) { + this.alias = alias; + return this; + } + public String gainSQLAliasWithQuote() { + String a = gainSQLAlias(); + String q = getQuote(); + // getTable 不能小写,因为Verifier用大小写敏感的名称判断权限 + // 如果要强制小写,则可在子类重写这个方法再 toLowerCase + // return q + (DATABASE_POSTGRESQL.equals(getDatabase()) ? a.toLowerCase() : a) + q; + return q + a + q; + } + + @Override + public String getGroup() { + return group; + } + public AbstractSQLConfig setGroup(String... keys) { + return setGroup(StringUtil.get(keys)); + } + @Override + public AbstractSQLConfig setGroup(String group) { + this.group = group; + return this; + } + + public String gainGroupString(boolean hasPrefix) { + //加上子表的 group + String joinGroup = ""; + if (joinList != null) { + boolean first = true; + for (Join join : joinList) { + if (join.isAppJoin()) { + continue; + } + + SQLConfig ocfg = join.getOnConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getGroup() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); + + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + //if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + //} + String c = ((AbstractSQLConfig) cfg).gainGroupString(false); + + if (StringUtil.isNotEmpty(c, true)) { + joinGroup += (first ? "" : ", ") + c; + first = false; + } + } + + ////先处理左/右关联,内关联忽略 + //SQLConfig outerConfig = join.getOuterConfig(); + //SQLConfig outerConfig2 = (outerConfig != null && outerConfig.getGroup() != null) || join.isLeftOrRightJoin() ? outerConfig : null; + // + //if (outerConfig2 != null) { + // outerConfig2.setMain(false).setKeyPrefix(true); + // //if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // // cfg.setAlias(cfg.getTable()); + // //} + // String c = ((AbstractSQLConfig) outerConfig2).gainGroupString(false); + // + // if (StringUtil.isNotEmpty(c, true)) { + // joinGroup += (first ? "" : ", ") + c; + // first = false; + // } + //} + } + } + + + group = StringUtil.trim(group); + String[] keys = StringUtil.split(group); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinGroup, true) ? "" : (hasPrefix ? " GROUP BY " : "") + joinGroup; + } + + for (int i = 0; i < keys.length; i++) { + if (isPrepared()) { + // 不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! + if (StringUtil.isName(keys[i]) == false) { + throw new IllegalArgumentException("@group:value 中 value里面用 , 分割的每一项都必须是1个单词!并且不要有空格!"); + } + } + + keys[i] = gainKey(keys[i]); + } + + return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.get(keys), joinGroup, ", "); + } + + @Override + public String getHavingCombine() { + return havingCombine; + } + @Override + public AbstractSQLConfig setHavingCombine(String havingCombine) { + this.havingCombine = havingCombine; + return this; + } + + @Override + public Map getHaving() { + return having; + } + @Override + public AbstractSQLConfig setHaving(Map having) { + this.having = having; + return this; + } + public AbstractSQLConfig setHaving(String... conditions) { + return setHaving(StringUtil.get(conditions)); + } + + /**TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } + * @return HAVING conditoin0 AND condition1 OR condition2 ... + * @throws Exception + */ + public String gainHavingString(boolean hasPrefix) throws Exception { + //加上子表的 having + String joinHaving = ""; + if (joinList != null) { + boolean first = true; + for (Join join : joinList) { + if (join.isAppJoin()) { + continue; + } + + SQLConfig ocfg = join.getOnConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getHaving() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); + + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + //if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + //} + String c = ((AbstractSQLConfig) cfg).gainHavingString(false); + + if (StringUtil.isNotEmpty(c, true)) { + joinHaving += (first ? "" : ", ") + c; + first = false; + } + } + } + } + + Map map = getHaving(); + Set> set = map == null ? null : map.entrySet(); + if (set == null || set.isEmpty()) { + return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; + } + + List raw = getRaw(); + // 提前把 @having& 转为 @having,或者干脆不允许 @raw:"@having&" boolean containRaw = raw != null && (raw.contains(KEY_HAVING) || raw.contains(KEY_HAVING_AND)); + boolean containRaw = raw != null && raw.contains(KEY_HAVING); + + // 直接把 having 类型从 Map 定改为 Map,避免额外拷贝 + // Map newMap = new LinkedHashMap<>(map.size()); + // for (Entry entry : set) { + // newMap.put(entry.getKey(), entry.getValue()); + // } + + //fun0(arg0,arg1,...);fun1(arg0,arg1,...) + String havingString = parseCombineExpression(getMethod(), getQuote(), getTable() + , getAlias(), map, getHavingCombine(), true, containRaw, true); + + return (hasPrefix ? " HAVING " : "") + StringUtil.concat(havingString, joinHaving, AND); + } + + protected String gainHavingItem(String quote, String table, String alias + , String key, String expression, boolean containRaw) throws Exception { + //fun(arg0,arg1,...) + if (containRaw) { + String rawSQL = gainRawSQL(KEY_HAVING, expression); + if (rawSQL != null) { + return rawSQL; + } + } + + if (expression.length() > 100) { + throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" + + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); + } + + int start = expression.indexOf("("); + if (start < 0) { + if (isPrepared() && PATTERN_FUNCTION.matcher(expression).matches() == false) { + throw new UnsupportedOperationException("字符串 " + expression + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 column?value 必须符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许空格!"); + } + return expression; + } + + int end = expression.lastIndexOf(")"); + if (start >= end) { + throw new IllegalArgumentException("字符 " + expression + " 不合法!" + + "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + } + + String method = expression.substring(0, start); + if (method.isEmpty() == false) { + if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { + if (StringUtil.isName(method) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); + } + } + else if (SQL_FUNCTION_MAP.containsKey(method) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + } + } + + return method + parseSQLExpression(KEY_HAVING, expression.substring(start), containRaw, false, null); + } + + @Override + public String getSample() { + return sample; + } + public AbstractSQLConfig setSample(String... conditions) { + return setSample(StringUtil.get(conditions)); + } + @Override + public AbstractSQLConfig setSample(String sample) { + this.sample = sample; + return this; + } + public String gainSampleString(boolean hasPrefix) { + //加上子表的 sample + String joinSample = ""; + if (joinList != null) { + boolean first = true; + for (Join join : joinList) { + if (join.isAppJoin()) { + continue; + } + + SQLConfig ocfg = join.getOnConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getSample() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); + + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + // if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + // } + String c = ((AbstractSQLConfig) cfg).gainSampleString(false); + + if (StringUtil.isNotEmpty(c, true)) { + joinSample += (first ? "" : ", ") + c; + first = false; + } + } + } + } + + String sample = StringUtil.trim(getSample()); + + String[] keys = StringUtil.split(sample); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinSample, true) ? "" : (hasPrefix ? " SAMPLE BY " : "") + joinSample; + } + + for (int i = 0; i < keys.length; i++) { + String item = keys[i]; + + String origin = item; - return (StringUtil.isEmpty(sch, true) ? "" : q + sch + q + ".") + q + sqlTable + q + ( isKeyPrefix() ? " AS " + getAliasWithQuote() : ""); - } - @Override - public AbstractSQLConfig setTable(String table) { //Table已经在Parser中校验,所以这里不用防SQL注入 - this.table = table; - return this; - } + if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 + if (StringUtil.isName(origin)) {} + else if (StringUtil.isCombineOfNumOrAlpha(origin)) { + continue; + } + else { + throw new IllegalArgumentException("预编译模式下 @sample:value 中 " + item + " 不合法! value 里面用 , 分割的" + + "每一项必须是 column 且其中 column 必须是 数字或英语字母组合!并且不要有多余的空格!"); + } + } - @Override - public String getAlias() { - return alias; - } - @Override - public AbstractSQLConfig setAlias(String alias) { - this.alias = alias; - return this; - } - public String getAliasWithQuote() { - String a = getAlias(); - if (StringUtil.isEmpty(a, true)) { - a = getTable(); + keys[i] = gainKey(origin); } - String q = getQuote(); - //getTable 不能小写,因为Verifier用大小写敏感的名称判断权限 - //如果要强制小写,则可在子类重写这个方法再 toLowerCase return q + (DATABASE_POSTGRESQL.equals(getDatabase()) ? a.toLowerCase() : a) + q; - return q + a + q; + + return (hasPrefix ? " SAMPLE BY " : "") + StringUtil.concat(StringUtil.get(keys), joinSample, ", "); } @Override - public String getGroup() { - return group; + public String getLatest() { + return latest; } - public AbstractSQLConfig setGroup(String... keys) { - return setGroup(StringUtil.getString(keys)); + public AbstractSQLConfig setLatest(String... conditions) { + return setLatest(StringUtil.get(conditions)); } @Override - public AbstractSQLConfig setGroup(String group) { - this.group = group; + public AbstractSQLConfig setLatest(String latest) { + this.latest = latest; return this; } - @JSONField(serialize = false) - public String getGroupString(boolean hasPrefix) { - //加上子表的 group - String joinGroup = ""; + public String gainLatestString(boolean hasPrefix) { + //加上子表的 latest + String joinLatest = ""; 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 cfg = (ocfg != null && ocfg.getGroup() != null) || j.isLeftOrRightJoin() ? ocfg : j.getJoinConfig(); + SQLConfig ocfg = join.getOnConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getLatest() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); if (cfg != null) { cfg.setMain(false).setKeyPrefix(true); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - String c = ((AbstractSQLConfig) cfg).getGroupString(false); + // if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + // } + String c = ((AbstractSQLConfig) cfg).gainLatestString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinGroup += (first ? "" : ", ") + c; + if (StringUtil.isNotEmpty(c, true)) { + joinLatest += (first ? "" : ", ") + c; first = false; } } } } + String latest = StringUtil.trim(getLatest()); - group = StringUtil.getTrimedString(group); - String[] keys = StringUtil.split(group); + String[] keys = StringUtil.split(latest); if (keys == null || keys.length <= 0) { - return StringUtil.isEmpty(joinGroup, true) ? "" : (hasPrefix ? " GROUP BY " : "") + joinGroup; + return StringUtil.isEmpty(joinLatest, true) ? "" : (hasPrefix ? " LATEST ON " : "") + joinLatest; } for (int i = 0; i < keys.length; i++) { - if (isPrepared()) { //不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! - if (StringUtil.isName(keys[i]) == false) { - throw new IllegalArgumentException("@group:value 中 value里面用 , 分割的每一项都必须是1个单词!并且不要有空格!"); + String item = keys[i]; + String origin = item; + + if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 + if (StringUtil.isName(origin) == false) { + throw new IllegalArgumentException("预编译模式下 @latest:value 中 " + item + " 不合法! value 里面用 , 分割的" + + "每一项必须是 column 且其中 column 必须是 英语单词!并且不要有多余的空格!"); } } - keys[i] = getKey(keys[i]); + keys[i] = gainKey(origin); } - return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); + return (hasPrefix ? " LATEST ON " : "") + StringUtil.concat(StringUtil.get(keys), joinLatest, ", "); } @Override - public String getHavingCombine() { - return havingCombine; - } - @Override - public SQLConfig setHavingCombine(String havingCombine) { - this.havingCombine = havingCombine; - return this; + public String getPartition() { + return partition; } - - @Override - public Map getHaving() { - return having; + public AbstractSQLConfig setPartition(String... conditions) { + return setPartition(StringUtil.get(conditions)); } @Override - public SQLConfig setHaving(Map having) { - this.having = having; + public AbstractSQLConfig setPartition(String partition) { + this.partition = partition; return this; } - public AbstractSQLConfig setHaving(String... conditions) { - return setHaving(StringUtil.getString(conditions)); - } - - /**TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } - * @return HAVING conditoin0 AND condition1 OR condition2 ... - * @throws Exception - */ - @JSONField(serialize = false) - public String getHavingString(boolean hasPrefix) throws Exception { - //加上子表的 having - String joinHaving = ""; + public String gainPartitionString(boolean hasPrefix) { + //加上子表的 partition + String joinPartition = ""; 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 cfg = (ocfg != null && ocfg.getHaving() != null) || j.isLeftOrRightJoin() ? ocfg : j.getJoinConfig(); + SQLConfig ocfg = join.getOnConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getPartition() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); if (cfg != null) { cfg.setMain(false).setKeyPrefix(true); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - String c = ((AbstractSQLConfig) cfg).getHavingString(false); + // if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + // } + String c = ((AbstractSQLConfig) cfg).gainPartitionString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinHaving += (first ? "" : ", ") + c; + if (StringUtil.isNotEmpty(c, true)) { + joinPartition += (first ? "" : ", ") + c; first = false; } } } } - Map map = getHaving(); - Set> set = map == null ? null : map.entrySet(); - if (set == null || set.isEmpty()) { - return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; + String partition = StringUtil.trim(getPartition()); + + String[] keys = StringUtil.split(partition); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinPartition, true) ? "" : (hasPrefix ? " PARTITION BY " : "") + joinPartition; } - List raw = getRaw(); - // 提前把 @having& 转为 @having,或者干脆不允许 @raw:"@having&" boolean containRaw = raw != null && (raw.contains(KEY_HAVING) || raw.contains(KEY_HAVING_AND)); - boolean containRaw = raw != null && raw.contains(KEY_HAVING); + for (int i = 0; i < keys.length; i++) { + String item = keys[i]; + String origin = item; - // 直接把 having 类型从 Map 定改为 Map,避免额外拷贝 - // Map newMap = new LinkedHashMap<>(map.size()); - // for (Entry entry : set) { - // newMap.put(entry.getKey(), entry.getValue()); - // } + if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 + if (StringUtil.isName(origin) == false) { + throw new IllegalArgumentException("预编译模式下 @partition:value 中 " + item + " 不合法! value 里面用 , 分割的" + + "每一项必须是 column 且其中 column 必须是 英语单词!并且不要有多余的空格!"); + } + } - //fun0(arg0,arg1,...);fun1(arg0,arg1,...) - String havingString = parseCombineExpression(getMethod(), getQuote(), getTable(), getAliasWithQuote(), map, getHavingCombine(), true, containRaw, true); + keys[i] = gainKey(origin); + } - return (hasPrefix ? " HAVING " : "") + StringUtil.concat(havingString, joinHaving, AND); + return (hasPrefix ? " PARTITION BY " : "") + StringUtil.concat(StringUtil.get(keys), joinPartition, ", "); } - 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); - if (rawSQL != null) { - return rawSQL; + @Override + public String getFill() { + return fill; + } + public AbstractSQLConfig setFill(String... conditions) { + return setFill(StringUtil.get(conditions)); + } + @Override + public AbstractSQLConfig setFill(String fill) { + this.fill = fill; + return this; + } + public String gainFillString(boolean hasPrefix) { + //加上子表的 fill + String joinFill = ""; + if (joinList != null) { + boolean first = true; + for (Join join : joinList) { + if (join.isAppJoin()) { + continue; + } + + SQLConfig ocfg = join.getOnConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getFill() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); + + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + // if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + // } + String c = ((AbstractSQLConfig) cfg).gainFillString(false); + + if (StringUtil.isNotEmpty(c, true)) { + joinFill += (first ? "" : ", ") + c; + first = false; + } + } } } - if (expression.length() > 100) { - throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" - + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); + String fill = StringUtil.trim(getFill()); + + String[] keys = StringUtil.split(fill); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinFill, true) ? "" : (hasPrefix ? " FILL(" : "") + joinFill + ")"; } - int start = expression.indexOf("("); - if (start < 0) { - if (isPrepared() && PATTERN_FUNCTION.matcher(expression).matches() == false) { - throw new UnsupportedOperationException("字符串 " + expression + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 column?value 必须符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许空格!"); + for (int i = 0; i < keys.length; i++) { + String item = keys[i]; + if ("NULL".equals(item) || "LINEAR".equals(item) || "PREV".equals(item) || "PREVIOUS".equals(item)) { + continue; } - return expression; - } - int end = expression.lastIndexOf(")"); - if (start >= end) { - throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); - } + String origin = item; - String method = expression.substring(0, start); - if (method.isEmpty() == false) { - if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { - if (StringUtil.isName(method) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); + if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 + if (StringUtil.isName(origin)) {} + else if (StringUtil.isCombineOfNumOrAlpha(origin)) { + continue; + } + else { + throw new IllegalArgumentException("预编译模式下 @fill:value 中 " + item + " 不合法! value 里面用 , 分割的" + + "每一项必须是 column 且其中 column 必须是 数字或英语字母组合!并且不要有多余的空格!"); } } - else if (SQL_FUNCTION_MAP.containsKey(method) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); - } - } - // String suffix = expression.substring(end + 1, expression.length()); - // - // 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; + keys[i] = gainKey(origin); + } - return method + parseSQLExpression(KEY_HAVING, expression.substring(start), containRaw, false, null); + return (hasPrefix ? " FILL(" : "") + StringUtil.concat(StringUtil.get(keys), joinFill, ", ") + ")"; } @Override public String getOrder() { return order; } - public AbstractSQLConfig setOrder(String... conditions) { - return setOrder(StringUtil.getString(conditions)); + public AbstractSQLConfig setOrder(String... conditions) { + return setOrder(StringUtil.get(conditions)); } @Override - public AbstractSQLConfig setOrder(String order) { + public AbstractSQLConfig setOrder(String order) { this.order = order; return this; } - @JSONField(serialize = false) - public String getOrderString(boolean hasPrefix) { + public String gainOrderString(boolean hasPrefix) { //加上子表的 order String joinOrder = ""; + String joinOuterOrder = ""; 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 cfg = (ocfg != null && ocfg.getOrder() != null) || j.isLeftOrRightJoin() ? ocfg : j.getJoinConfig(); + SQLConfig ocfg = join.getOnConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getOrder() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); if (cfg != null) { cfg.setMain(false).setKeyPrefix(true); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - String c = ((AbstractSQLConfig) cfg).getOrderString(false); + //if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + //} + String c = ((AbstractSQLConfig) cfg).gainOrderString(false); - if (StringUtil.isEmpty(c, true) == false) { + if (StringUtil.isNotEmpty(c, true)) { joinOrder += (first ? "" : ", ") + c; first = false; } @@ -1360,19 +2061,21 @@ public abstract class AbstractSQLConfig implements SQLConfig { } - String order = StringUtil.getTrimedString(getOrder()); + String order = StringUtil.trim(getOrder()); // SELECT * FROM sys.Moment ORDER BY userId ASC, rand(); 前面的 userId ASC 和后面的 rand() 都有效 // if ("rand()".equals(order)) { // return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(order, joinOrder, ", "); // } - if (getCount() > 0 && (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; //让数据库调控默认升序还是降序 + "+"; } @@ -1428,10 +2131,20 @@ public abstract class AbstractSQLConfig implements SQLConfig { } } - keys[i] = getKey(origin) + sort; + keys[i] = gainKey(origin) + sort; } - return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinOrder, ", "); + return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(StringUtil.get(keys), joinOrder, ", "); + } + + @Override + public Map getKeyMap() { + return keyMap; + } + @Override + public AbstractSQLConfig setKeyMap(Map keyMap) { + this.keyMap = keyMap; + return this; } @Override @@ -1439,7 +2152,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return raw; } @Override - public SQLConfig setRaw(List raw) { + public AbstractSQLConfig setRaw(List raw) { this.raw = raw; return this; } @@ -1451,8 +2164,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @throws Exception */ @Override - public String getRawSQL(String key, Object value) throws Exception { - return getRawSQL(key, value, false); + public String gainRawSQL(String key, Object value) throws Exception { + return gainRawSQL(key, value, ! ALLOW_MISSING_KEY_4_COMBINE); } /**获取原始 SQL 片段 * @param key @@ -1462,7 +2175,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @throws Exception */ @Override - public String getRawSQL(String key, Object value, boolean throwWhenMissing) throws Exception { + public String gainRawSQL(String key, Object value, boolean throwWhenMissing) throws Exception { if (value == null) { return null; } @@ -1481,6 +2194,9 @@ public abstract class AbstractSQLConfig implements SQLConfig { throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + "对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); } + + putWarnIfNeed(JSONMap.KEY_RAW, "@raw:value 的 value 中 " + + key + " 不合法!对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); } else if (rawSQL.isEmpty()) { return (String) value; @@ -1496,18 +2212,18 @@ public abstract class AbstractSQLConfig implements SQLConfig { return json; } @Override - public AbstractSQLConfig setJson(List json) { + public AbstractSQLConfig setJson(List json) { this.json = json; return this; } @Override - public Subquery getFrom() { + public Subquery getFrom() { return from; } @Override - public AbstractSQLConfig setFrom(Subquery from) { + public AbstractSQLConfig setFrom(Subquery from) { this.from = from; return this; } @@ -1517,17 +2233,17 @@ public abstract class AbstractSQLConfig implements SQLConfig { return column; } @Override - public AbstractSQLConfig setColumn(List column) { + public AbstractSQLConfig setColumn(List column) { this.column = column; return this; } - @JSONField(serialize = false) - public String getColumnString() throws Exception { - return getColumnString(false); + public String gainColumnString() throws Exception { + return gainColumnString(false); } - @JSONField(serialize = false) - public String getColumnString(boolean inSQLJoin) throws Exception { + public String gainColumnString(boolean inSQLJoin) throws Exception { List column = getColumn(); + String as = gainAs(); + String q = getQuote(); switch (getMethod()) { case HEAD: @@ -1539,7 +2255,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { for (String c : column) { if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 + if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 //排除@raw中的值,以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错 //column.remove(c); continue; @@ -1551,19 +2267,23 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 +2304,24 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 ? gainKey(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,10 +2331,11 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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)); + s += ((pfirst ? "" : ",") + gainKey(c)); pfirst = false; } @@ -1626,31 +2346,31 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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.getOnConfig(); 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.gainSQLAlias(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) { + String c = ((AbstractSQLConfig) cfg).gainColumnString(true); + if (StringUtil.isNotEmpty(c, true)) { joinColumn += (first ? "" : ", ") + c; first = false; } @@ -1661,20 +2381,18 @@ public abstract class AbstractSQLConfig implements SQLConfig { } } - String tableAlias = getAliasWithQuote(); - - // String c = StringUtil.getString(column); //id,name;json_length(contactIdList):contactCount;... + String tableAlias = q + gainSQLAlias() + 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) { boolean noColumn = column != null && inSQLJoin; - String mc = isKeyPrefix() == false ? (noColumn ? "" : "*") : (noColumn ? "" : tableAlias + ".*"); + String mc = isKeyPrefix() ? (noColumn ? "" : tableAlias + ".*") : (noColumn ? "" : "*"); return StringUtil.concat(mc, joinColumn, ", ", true); } - List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_COLUMN); @@ -1683,31 +2401,32 @@ public abstract class AbstractSQLConfig implements SQLConfig { String expression = keys[i]; //fun(arg0,arg1,...) if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 + if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 continue; } // 简单点, 后台配置就带上 AS - // int index = expression.lastIndexOf(":"); - // String alias = expression.substring(index+1); - // boolean hasAlias = StringUtil.isName(alias); - // String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; - // if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 - // expression = pre + (hasAlias ? " AS " + alias : ""); - // continue; - // } + 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 ? gainAs() + 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); + String c = StringUtil.get(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 +2440,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @param expression * @param containRaw * @param allowAlias - * @param columnPrefix * @return */ public String parseSQLExpression(String key, String expression, boolean containRaw, boolean allowAlias) { @@ -1732,17 +2450,26 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @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) { //没有函数 ,可能是字段,也可能是 DISTINCT xx String[] cks = parseArgsSplitWithComma(expression, true, containRaw, allowAlias); - expression = StringUtil.getString(cks); + expression = StringUtil.get(cks); } else { // FIXME 用括号断开? 如果少的话,用关键词加括号断开,例如 )OVER( 和 )AGAINST( // 窗口函数 rank() OVER (PARTITION BY id ORDER BY userId ASC) // 全文索引 math(name,tag) AGAINST ('a b +c -d' IN NATURALE LANGUAGE MODE) // IN BOOLEAN MODE @@ -1790,15 +2517,15 @@ public abstract class AbstractSQLConfig implements SQLConfig { } 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()); } // 解析函数内的参数 String ckeys[] = parseArgsSplitWithComma(s, false, containRaw, allowAlias); - String suffix = expression.substring(end + 1, expression.length()); //:contactCount + String suffix = expression.substring(end + 1); //:contactCount String alias = null; if (allowAlias) { int index = suffix.lastIndexOf(":"); @@ -1811,15 +2538,15 @@ public abstract class AbstractSQLConfig implements SQLConfig { } } - if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") - || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + if (suffix.isEmpty() == false && (suffix.contains("--") || suffix.contains("/*") + || PATTERN_RANGE.matcher(suffix).matches() == false)) { throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!预编译模式下 " + key + ":\"column?value;function(arg0,arg1,...)?value...\"" + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); } - String origin = fun + "(" + (distinct ? PREFFIX_DISTINCT : "") + StringUtil.getString(ckeys) + ")" + suffix; - expression = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); + String origin = fun + "(" + (distinct ? PREFIX_DISTINCT : "") + StringUtil.get(ckeys) + ")" + suffix; + expression = origin + (StringUtil.isEmpty(alias, true) ? "" : gainAs() + quote + alias + quote); } else { //是窗口函数 fun(arg0,agr1) OVER (agr0 agr1 ...) @@ -1844,55 +2571,60 @@ public abstract class AbstractSQLConfig implements SQLConfig { } } else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { - throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); } } // 获取前半部分函数的参数解析 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); // 后半部分的参数 // 别名 - int aliasIndex = allowAlias == false ? -1 : s2.lastIndexOf(":"); + int aliasIndex = allowAlias ? s2.lastIndexOf(":") : -1; String alias = aliasIndex < 0 ? "" : s2.substring(aliasIndex + 1); if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { throw new IllegalArgumentException("字符串 " + alias + " 不合法!预编译模式下 " + key + ":value 中 value里面用 ; 分割的每一项" + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); } - + String suffix = s2.substring(end + 1, aliasIndex < 0 ? s2.length() : aliasIndex); - if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") - || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + if (suffix.isEmpty() == false && (suffix.contains("--") || suffix.contains("/*") + || PATTERN_RANGE.matcher(suffix).matches() == false)) { throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!预编译模式下 " + key + ":\"column?value;function(arg0,arg1,...)?value...\"" + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); } - + // 获取后半部分的参数解析 (agr0 agr1 ...) - String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw, allowAlias); - expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") // 传参不传空格,拼接带空格 - + StringUtil.getString(argsString2) + ")" + suffix + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } + String[] argsString2 = parseArgsSplitWithComma(argString2, false, containRaw, allowAlias); + expression = fun + "(" + StringUtil.get(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") + + StringUtil.get(argsString2) + ")" + suffix // 传参不传空格,拼接带空格 + + (StringUtil.isEmpty(alias, true) ? "" : gainAs() + 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(); - String ckeys[] = StringUtil.split(param); // 以","分割参数 + boolean isKeyPrefix = isKeyPrefix(); + String tableAlias = quote + gainSQLAlias() + quote; + String[] ckeys = StringUtil.split(param); // 以","分割参数 if (ckeys != null && ckeys.length > 0) { for (int i = 0; i < ckeys.length; i++) { @@ -1900,7 +2632,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { String origin; String alias; - + // 如果参数包含 "'" ,解析字符串 if (ck.startsWith("`") && ck.endsWith("`")) { origin = ck.substring(1, ck.length() - 1); @@ -1911,7 +2643,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { + " 中所有字符串 column 都必须必须为1个单词 !"); } - origin = getKey(origin).toString(); + origin = gainKey(origin); } else if (ck.startsWith("'") && ck.endsWith("'")) { origin = ck.substring(1, ck.length() - 1); @@ -1922,7 +2654,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { } // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 - origin = getValue(origin).toString(); + origin = gainValue(origin).toString(); } else { // 参数不包含",",即不是字符串 @@ -1949,10 +2681,13 @@ public abstract class AbstractSQLConfig implements SQLConfig { + "关键字必须全大写,且以空格分隔的参数,空格必须只有 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,32 +2696,45 @@ public abstract class AbstractSQLConfig implements SQLConfig { // 以空格分割参数 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 != null) { // newSQLConfig 提前处理好的 if (mk.length() > 0) { origin = mk; } - } else if (StringUtil.isNumer(origin)) { + } else if (StringUtil.isNumber(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 = gainValue(origin).toString(); + } } - if (isColumn && StringUtil.isEmpty(alias, true) == false) { - origin += " AS " + quote + alias + quote; + if (isColumn && StringUtil.isNotEmpty(alias, true)) { + origin += gainAs() + quote + alias + quote; } } } @@ -2006,9 +2754,10 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @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 + gainSQLAlias() + quote; // 包含空格的参数 肯定不包含别名 不用处理别名 if (mkes != null && mkes.length > 0) { @@ -2017,7 +2766,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { String origin = mkes[j]; String mk = RAW_MAP.get(origin); - if (mk != null) { // newSQLConfig 提前处理好的 + if (mk != null) { // newSQLConfig 提前处理好的 if (mk.length() > 0) { mkes[j] = mk; } @@ -2035,7 +2784,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { + " 中所有字符串 column 都必须必须为1个单词 !"); } - mkes[j] = getKey(origin).toString(); + mkes[j] = gainKey(origin); continue; } else if (ck.startsWith("'") && ck.endsWith("'")) { @@ -2047,27 +2796,43 @@ public abstract class AbstractSQLConfig implements SQLConfig { } // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 - mkes[j] = getValue(origin).toString(); + mkes[j] = gainValue(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)) { + if (StringUtil.isNumber(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 = gainValue(origin).toString(); + } } mkes[j] = origin; @@ -2082,7 +2847,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { public List> getValues() { return values; } - @JSONField(serialize = false) public String getValuesString() { String s = ""; if (values != null && values.size() > 0) { @@ -2096,16 +2860,16 @@ public abstract class AbstractSQLConfig implements SQLConfig { items[i] = "("; for (int j = 0; j < vs.size(); j++) { - items[i] += ((j <= 0 ? "" : ",") + getValue(vs.get(j))); + items[i] += ((j <= 0 ? "" : ",") + gainValue(vs.get(j))); } items[i] += ")"; } - s = StringUtil.getString(items); + s = StringUtil.get(items); } return s; } @Override - public AbstractSQLConfig setValues(List> valuess) { + public AbstractSQLConfig setValues(List> valuess) { this.values = valuess; return this; } @@ -2115,7 +2879,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return content; } @Override - public AbstractSQLConfig setContent(Map content) { + public AbstractSQLConfig setContent(Map content) { this.content = content; return this; } @@ -2125,7 +2889,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return count; } @Override - public AbstractSQLConfig setCount(int count) { + public AbstractSQLConfig setCount(int count) { this.count = count; return this; } @@ -2134,7 +2898,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return page; } @Override - public AbstractSQLConfig setPage(int page) { + public AbstractSQLConfig setPage(int page) { this.page = page; return this; } @@ -2143,7 +2907,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return position; } @Override - public AbstractSQLConfig setPosition(int position) { + public AbstractSQLConfig setPosition(int position) { this.position = position; return this; } @@ -2153,7 +2917,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return query; } @Override - public AbstractSQLConfig setQuery(int query) { + public AbstractSQLConfig setQuery(int query) { this.query = query; return this; } @@ -2162,7 +2926,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return compat; } @Override - public AbstractSQLConfig setCompat(Boolean compat) { + public AbstractSQLConfig setCompat(Boolean compat) { this.compat = compat; return this; } @@ -2172,7 +2936,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return type; } @Override - public AbstractSQLConfig setType(int type) { + public AbstractSQLConfig setType(int type) { this.type = type; return this; } @@ -2182,39 +2946,40 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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) { int cache2; if (cache == null) { - cache2 = JSONRequest.CACHE_ALL; + cache2 = JSONMap.CACHE_ALL; } else { // if (isSubquery) { - // throw new IllegalArgumentException("子查询内不支持传 " + JSONRequest.KEY_CACHE + "!"); + // throw new IllegalArgumentException("子查询内不支持传 " + apijson.JSONMap.KEY_CACHE + "!"); // } switch (cache) { case "0": - case JSONRequest.CACHE_ALL_STRING: - cache2 = JSONRequest.CACHE_ALL; + case JSONMap.CACHE_ALL_STRING: + cache2 = JSONMap.CACHE_ALL; break; case "1": - case JSONRequest.CACHE_ROM_STRING: - cache2 = JSONRequest.CACHE_ROM; + case JSONMap.CACHE_ROM_STRING: + cache2 = JSONMap.CACHE_ROM; break; case "2": - case JSONRequest.CACHE_RAM_STRING: - cache2 = JSONRequest.CACHE_RAM; + case JSONMap.CACHE_RAM_STRING: + cache2 = JSONMap.CACHE_RAM; break; default: - throw new IllegalArgumentException(JSONRequest.KEY_CACHE + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); + throw new IllegalArgumentException(JSONMap.KEY_CACHE + + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); } } return cache2; @@ -2225,17 +2990,17 @@ public abstract class AbstractSQLConfig implements SQLConfig { return explain; } @Override - public AbstractSQLConfig setExplain(boolean explain) { + public AbstractSQLConfig setExplain(boolean explain) { this.explain = explain; return this; } @Override - public List getJoinList() { + public List> getJoinList() { return joinList; } @Override - public SQLConfig setJoinList(List joinList) { + public AbstractSQLConfig setJoinList(List> joinList) { this.joinList = joinList; return this; } @@ -2250,7 +3015,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return test; } @Override - public AbstractSQLConfig setTest(boolean test) { + public AbstractSQLConfig setTest(boolean test) { this.test = test; return this; } @@ -2258,7 +3023,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { /**获取初始位置offset * @return */ - @JSONField(serialize = false) public int getOffset() { return getOffset(getPage(), getCount()); } @@ -2273,22 +3037,69 @@ public abstract class AbstractSQLConfig implements SQLConfig { /**获取限制数量 * @return */ - @JSONField(serialize = false) - public String getLimitString() { - if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { + public String gainLimitString() { + int count = getCount(); + int page = getPage(); + + boolean isMilvus = isMilvus(); + if ((count <= 0 && ! (isMilvus && isMain())) || RequestMethod.isHeadMethod(getMethod(), true)) { // TODO HEAD 真的不需要 LIMIT ? return ""; } - return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2(), isOracle()); - } - /**获取限制数量 - * @param limit - * @return - */ - public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle) { + + boolean isSurrealDB = isSurrealDB(); + boolean isQuestDB = isQuestDB(); + if (isSurrealDB || isQuestDB || isMilvus) { + if (count == 0) { + Parser parser = gainParser(); + count = parser == null ? AbstractParser.MAX_QUERY_COUNT : parser.getMaxQueryCount(); + } + + int offset = getOffset(page, count); + if (isQuestDB()) { + return " LIMIT " + offset + ", " + (offset + count); + } + else if (isSurrealDB()) { + return " START " + offset + " LIMIT " + count; + } + else { + return " LIMIT " + offset + ", " + count; // 目前 moql-transx 的限制 + } + } + + boolean isOracle = isOracle(); + return gainLimitString(page, count, isTSQL(), isOracle || isDameng() || isKingBase(), isPresto() || isTrino()); + } + /**获取限制数量及偏移量 + * @param page + * @param count + * @param isTSQL + * @param isOracle + * @return + */ + public static String gainLimitString(int page, int count, boolean isTSQL, boolean isOracle) { + return gainLimitString(page, count, isTSQL, isOracle, false); + } + /**获取限制数量及偏移量 + * @param page + * @param count + * @param isTSQL + * @param isOracle + * @param isPresto + * @return + */ + public static String gainLimitString(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 +3110,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return nulls; } @Override - public SQLConfig setNull(List nulls) { + public AbstractSQLConfig setNull(List nulls) { this.nulls = nulls; return this; } @@ -2309,7 +3120,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return cast; } @Override - public SQLConfig setCast(Map cast) { + public AbstractSQLConfig setCast(Map cast) { this.cast = cast; return this; } @@ -2341,7 +3152,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return combine; } @Override - public AbstractSQLConfig setCombine(String combine) { + public AbstractSQLConfig setCombine(String combine) { this.combine = combine; return this; } @@ -2360,7 +3171,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return combineMap; } @Override - public AbstractSQLConfig setCombineMap(Map> combineMap) { + public AbstractSQLConfig setCombineMap(Map> combineMap) { this.combineMap = combineMap; return this; } @@ -2370,7 +3181,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { return where; } @Override - public AbstractSQLConfig setWhere(Map where) { + public AbstractSQLConfig setWhere(Map where) { this.where = where; return this; } @@ -2380,7 +3191,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @param key * @return */ - @JSONField(serialize = false) @Override public Object getWhere(String key) { return getWhere(key, false); @@ -2392,7 +3202,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @return *

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

*/ - @JSONField(serialize = false) @Override public Object getWhere(String key, boolean exactMatch) { if (exactMatch) { @@ -2415,10 +3224,10 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 +3297,10 @@ public abstract class AbstractSQLConfig implements SQLConfig { } 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,11 +3311,10 @@ public abstract class AbstractSQLConfig implements SQLConfig { /**获取WHERE * @return - * @throws Exception + * @throws Exception */ - @JSONField(serialize = false) @Override - public String getWhereString(boolean hasPrefix) throws Exception { + public String gainWhereString(boolean hasPrefix) throws Exception { String combineExpr = getCombine(); if (StringUtil.isEmpty(combineExpr, false)) { return getWhereString(hasPrefix, getMethod(), getWhere(), getCombineMap(), getJoinList(), ! isTest()); @@ -2516,12 +3325,12 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @param method * @param where * @return - * @throws Exception + * @throws Exception */ - @JSONField(serialize = false) - public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, String combine, List joinList, boolean verifyName) throws Exception { - - 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 +3346,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @param quote * @param table * @param alias - * @param conditioinMap where 或 having 对应条件的 Map + * @param conditionMap where 或 having 对应条件的 Map * @param combine * @param verifyName * @param containRaw @@ -2546,20 +3355,20 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @throws Exception */ protected String parseCombineExpression(RequestMethod method, String quote, String table, String alias - , Map conditioinMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception { + , Map conditionMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception { String errPrefix = table + (isHaving ? ":{ @having:{ " : ":{ ") + "@combine:'" + combine + (isHaving ? "' } }" : "' }"); - String s = StringUtil.getString(combine); + String s = StringUtil.get(combine); if (s.startsWith(" ") || s.endsWith(" ") ) { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!不允许首尾有空格,也不允许连续空格!空格不能多也不能少!" + "逻辑连接符 & | 左右必须各一个相邻空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); } - 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 +3378,16 @@ public abstract class AbstractSQLConfig implements SQLConfig { String result = ""; - List prepreadValues = getPreparedValueList(); + List preparedValues = getPreparedValueList(); + if (preparedValues == null && isHaving == false) { + preparedValues = new ArrayList<>(); + } Map usedKeyCountMap = new HashMap<>(size); int n = s.length(); if (n > 0) { - if (isHaving == false) { + if (isHaving == false) { // 只收集表达式条件值 setPreparedValueList(new ArrayList<>()); // 必须反过来,否则 JOIN ON 内部 @combine 拼接后顺序错误 } @@ -2610,7 +3422,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 +3431,31 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 ? gainHavingItem(quote, table, alias, column, (String) value, containRaw) + : gainWhereItem(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,11 +3465,12 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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); - result += "( " + getCondition(isNot, wi) + " )"; + result += "( " + gainCondition(isNot, wi) + " )"; isNot = false; first = false; } @@ -2672,7 +3496,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { result += SQL.AND; lastLogic = c; i ++; - } + } else { key += c; } @@ -2680,7 +3504,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 +3525,13 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 +3561,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { depth ++; if (depth > maxDepth && maxDepth > 0) { - throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!"); } @@ -2751,7 +3578,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { result += c; lastLogic = 0; - } + } else { key += c; } @@ -2766,7 +3593,12 @@ public abstract class AbstractSQLConfig implements SQLConfig { } } - Set> set = conditioinMap.entrySet(); + List exprPreparedValues = getPreparedValueList(); + if (isHaving == false) { // 只收集 AND 条件值 + setPreparedValueList(new ArrayList<>()); + } + + Set> set = conditionMap.entrySet(); String andCond = ""; boolean isItemFirst = true; @@ -2777,7 +3609,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { continue; } - String wi = isHaving ? getHavingItem(quote, table, alias, key, (String) entry.getValue(), containRaw) : getWhereItem(key, entry.getValue(), method, verifyName); + String wi = isHaving ? gainHavingItem(quote, table, alias, key, (String) entry.getValue(), containRaw) + : gainWhereItem(key, entry.getValue(), method, verifyName); if (StringUtil.isEmpty(wi, true)) {//避免SQL条件连接错误 continue; } @@ -2786,26 +3619,34 @@ public abstract class AbstractSQLConfig implements SQLConfig { isItemFirst = false; } + if (isHaving == false) { // 优先存放 AND 条件值 + preparedValues.addAll(getPreparedValueList()); + } + if (StringUtil.isEmpty(result, true)) { result = andCond; } else if (StringUtil.isNotEmpty(andCond, true)) { // andCond 必须放后面,否则 prepared 值顺序错误 - if (isHaving) { // HAVING 前 WHERE 已经有条件 ? 占位,不能反过来,想优化 AND 连接在最前,需要多遍历一次内部的 key,也可以 newSQLConfig 时存到 andList + 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 +3656,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @return * @throws Exception */ - @Deprecated - public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { + public String getWhereString(boolean hasPrefix, RequestMethod method, Map where + , Map> combine, List> joinList, boolean verifyName) throws Exception { Set>> combineSet = combine == null ? null : combine.entrySet(); if (combineSet == null || combineSet.isEmpty()) { Log.w(TAG, "getWhereString combineSet == null || combineSet.isEmpty() >> return \"\";"); @@ -2853,7 +3694,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { isItemFirst = true; cs = ""; for (String key : keyList) { - c = getWhereItem(key, where.get(key), method, verifyName); + c = gainWhereItem(key, where.get(key), method, verifyName); if (StringUtil.isEmpty(c, true)) {//避免SQL条件连接错误 continue; @@ -2884,7 +3725,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { protected String concatJoinWhereString(String whereString) throws Exception { - List joinList = getJoinList(); + List> joinList = getJoinList(); if (joinList != null) { String newWs = ""; @@ -2893,12 +3734,13 @@ public abstract class AbstractSQLConfig implements SQLConfig { List newPvl = new ArrayList<>(); List pvl = new ArrayList<>(getPreparedValueList()); - SQLConfig jc; + SQLConfig jc; + SQLConfig outerConfig; String js; - + boolean isWsEmpty = StringUtil.isEmpty(ws, true); boolean changed = false; - //各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? - for (Join j : joinList) { + // 各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? + for (Join j : joinList) { String jt = j.getJoinType(); switch (jt) { @@ -2906,26 +3748,48 @@ public abstract class AbstractSQLConfig implements SQLConfig { case "@": // APP JOIN case "<": // LEFT JOIN case ">": // RIGHT JOIN + outerConfig = j.getOuterConfig(); + if (outerConfig == null){ + break; + } + boolean isMain1 = outerConfig.isMain(); + outerConfig.setMain(false).setPrepared(isPrepared()).setPreparedValueList(new ArrayList()); + String outerWhere = outerConfig.gainWhereString(false); + + int logic1 = Logic.getType(jt); + newWs += " ( " + + gainCondition( + Logic.isNot(logic1), + ws + + ( isWsEmpty ? "" : (Logic.isAnd(logic1) ? AND : OR) ) + + " ( " + outerWhere + " ) " + ) + + " ) "; + newPvl.addAll(pvl); + newPvl.addAll(outerConfig.getPreparedValueList()); + + 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 case ")": // FOREIGN JOIN: B & ! A + case "~": // ASOF JOIN: B ~= A jc = j.getJoinConfig(); boolean isMain = jc.isMain(); jc.setMain(false).setPrepared(isPrepared()).setPreparedValueList(new ArrayList()); - js = jc.getWhereString(false); + js = jc.gainWhereString(false); jc.setMain(isMain); boolean isOuterJoin = "!".equals(jt); boolean isSideJoin = "^".equals(jt); boolean isAntiJoin = "(".equals(jt); boolean isForeignJoin = ")".equals(jt); - boolean isWsEmpty = StringUtil.isEmpty(ws, true); + //boolean isWsEmpty = StringUtil.isEmpty(ws, true); if (isWsEmpty) { if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) @@ -2951,7 +3815,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { } else { if (isSideJoin || isForeignJoin) { - newWs += " ( " + getCondition(true, ws) + " ) "; + newWs += " ( " + gainCondition(true, ws) + " ) "; newPvl.addAll(pvl); newPvl.addAll(jc.getPreparedValueList()); @@ -2962,11 +3826,11 @@ public abstract class AbstractSQLConfig implements SQLConfig { continue; } - if (StringUtil.isEmpty(newWs, true) == false) { + if (StringUtil.isNotEmpty(newWs, true)) { 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 @@ -2974,16 +3838,16 @@ public abstract class AbstractSQLConfig implements SQLConfig { } else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) //MySQL 因为 NULL 值处理问题,(A & ! B) | (B & ! A) 与 ! (A & B) 返回结果不一样,后者往往更多 - newWs += " ( " + getCondition( - true, + newWs += " ( " + gainCondition( + true, ( isWsEmpty ? "" : ws + AND ) + " ( " + js + " ) " ) + " ) "; } else { // & INNER JOIN: A & B; | FULL JOIN: A | B; OUTER JOIN: ! (A | B) int logic = Logic.getType(jt); newWs += " ( " - + getCondition( - Logic.isNot(logic), + + gainCondition( + Logic.isNot(logic), ws + ( isWsEmpty ? "" : (Logic.isAnd(logic) ? AND : OR) ) + " ( " + js + " ) " @@ -3000,7 +3864,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { throw new UnsupportedOperationException( "join:value 中 value 里的 " + jt + "/" + j.getPath() + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" - + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" + + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN, ~ ASOF ] 之外的 JOIN 类型 !" ); } } @@ -3023,23 +3887,26 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @return * @throws Exception */ - protected String getWhereItem(String key, Object value, RequestMethod method, boolean verifyName) throws Exception { + protected String gainWhereItem(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 } @@ -3070,93 +3937,122 @@ public abstract class AbstractSQLConfig implements SQLConfig { keyType = 0; } - String column = getRealKey(method, key, false, true, verifyName); - + String column = gainRealKey(method, key, false, true, verifyName); + // 原始 SQL 片段 - String rawSQL = getRawSQL(key, value, keyType != 4 || value instanceof String == false); + String rawSQL = gainRawSQL(key, value); switch (keyType) { case 1: - return getSearchString(key, column, value, rawSQL); + return gainSearchString(key, column, value, rawSQL); case -2: case 2: - return getRegExpString(key, column, value, keyType < 0, rawSQL); + return gainRegExpString(key, column, value, keyType < 0, rawSQL); case 3: - return getBetweenString(key, column, value, rawSQL); + return gainBetweenString(key, column, value, rawSQL); case 4: - return getRangeString(key, column, value, rawSQL); + return gainRangeString(key, column, value, rawSQL); case 5: - return getExistsString(key, column, value, rawSQL); + return gainExistsString(key, column, value, rawSQL); case 6: - return getContainString(key, column, value, rawSQL); + return gainContainString(key, column, value, rawSQL); case 7: - return getCompareString(key, column, value, ">=", rawSQL); + return gainCompareString(key, column, value, ">=", rawSQL); case 8: - return getCompareString(key, column, value, "<=", rawSQL); + return gainCompareString(key, column, value, "<=", rawSQL); case 9: - return getCompareString(key, column, value, ">", rawSQL); + return gainCompareString(key, column, value, ">", rawSQL); case 10: - return getCompareString(key, column, value, "<", rawSQL); + return gainCompareString(key, column, value, "<", rawSQL); default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! - return getEqualString(key, column, value, rawSQL); + return gainEqualString(key, column, value, rawSQL); } } - @JSONField(serialize = false) - public String getEqualString(String key, String column, Object value, String rawSQL) throws Exception { - if (value != null && JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { + public String gainEqualString(String key, String column, Object value, String rawSQL) throws Exception { + if (value != null && JSON.isBoolOrNumOrStr(value) == false && value instanceof Subquery == false) { 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 gainKey(column) + logic + (value instanceof Subquery ? gainSubqueryString((Subquery) value) + : (rawSQL != null ? rawSQL : gainValue(key, column, value))); } - @JSONField(serialize = false) - public String getCompareString(String key, String column, Object value, String type, String rawSQL) throws Exception { - if (value != null && JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { + public String gainCompareString(String key, String column, Object value, String type, String rawSQL) throws Exception { + if (value != null && JSON.isBoolOrNumOrStr(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 gainKey(column) + " " + type + " " + (value instanceof Subquery ? gainSubqueryString((Subquery) value) + : (rawSQL != null ? rawSQL : gainValue(key, column, value))); } - public String getKey(String key) { - if (isTest()) { + public String gainKey(@NotNull String key) { + 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 gainSQLValue(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 = gainSQLKey(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) { + public String gainSQLKey(String key) { String q = getQuote(); - return (isKeyPrefix() ? getAliasWithQuote() + "." : "") + q + key + q; + return (isKeyPrefix() ? q + gainSQLAlias() + q + "." : "") + q + key + q; } /** * 使用prepareStatement预编译,值为 ? ,后续动态set进去 */ - protected Object getValue(@NotNull Object value) { - return getValue(null, null, value); + protected Object gainValue(@NotNull Object value) { + return gainValue(null, null, value); } protected List preparedValueList = new ArrayList<>(); - protected Object getValue(String key, String column, Object value) { + protected Object gainValue(String key, String column, Object value) { if (isPrepared()) { if (value == null) { return null; @@ -3186,21 +4082,23 @@ public abstract class AbstractSQLConfig implements SQLConfig { return StringUtil.isEmpty(type, true) ? "?" : "cast(?" + SQL.AS + type + ")"; } - return key == null ? getSQLValue(value) : getSQLValue(key, column, value); + return key == null ? gainSQLValue(value) : gainSQLValue(key, column, value); } - public Object getSQLValue(String key, String column, @NotNull Object value) { + public Object gainSQLValue(String key, String column, @NotNull Object value) { Map castMap = getCast(); String type = key == null || castMap == null ? null : castMap.get(key); - Object val = getSQLValue(value); + Object val = gainSQLValue(value); return StringUtil.isEmpty(type, true) ? val : "cast(" + val + SQL.AS + type + ")"; } - public Object getSQLValue(@NotNull Object value) { + public Object gainSQLValue(@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 +4106,24 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 - */ - @JSONField(serialize = false) - public String getSearchString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { + * @param key + * @param column + * @param value + * @param rawSQL + * @return {@link #gainSearchString(String, String, Object[], int)} + * @throws IllegalArgumentException + */ + public String gainSearchString(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 ""; @@ -3232,19 +4133,21 @@ public abstract class AbstractSQLConfig implements SQLConfig { column = logic.getKey(); Log.i(TAG, "getSearchString column = " + column); - JSONArray arr = newJSONArray(value); + List arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } - return getSearchString(key, column, arr.toArray(), logic.getType()); + return gainSearchString(key, column, arr.toArray(), logic.getType()); } /**search key match values - * @param in - * @return LOGIC [ key LIKE 'values[i]' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getSearchString(String key, String column, Object[] values, int type) throws IllegalArgumentException { + * @param key + * @param column + * @param values + * @param type + * @return LOGIC [ key LIKE 'values[i]' ] + * @throws IllegalArgumentException + */ + public String gainSearchString(String key, String column, Object[] values, int type) throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; } @@ -3262,10 +4165,10 @@ public abstract class AbstractSQLConfig implements SQLConfig { // throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); // } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, column, (String) v); + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + gainLikeString(key, column, (String) v); } - return getCondition(Logic.isNot(type), condition); + return gainCondition(Logic.isNot(type), condition); } /**WHERE key LIKE 'value' @@ -3274,8 +4177,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @param value * @return key LIKE 'value' */ - @JSONField(serialize = false) - public String getLikeString(@NotNull String key, @NotNull String column, String value) { + public String gainLikeString(@NotNull String key, @NotNull String column, String value) { String k = key.substring(0, key.length() - 1); char r = k.charAt(k.length() - 1); @@ -3286,7 +4188,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 +4211,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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("\\\\", "\\\\\\\\"); @@ -3322,7 +4226,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { } } - return getKey(column) + " LIKE " + getValue(key, column, value); + return gainKey(column) + " LIKE " + gainValue(key, column, value); } //$ search >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -3334,14 +4238,15 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @param key * @param column * @param value - * @param ignoreCase - * @return {@link #getRegExpString(String, Object[], int, boolean)} - * @throws IllegalArgumentException + * @param ignoreCase + * @return {@link #gainRegExpString(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 gainRegExpString(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 ""; @@ -3351,22 +4256,22 @@ public abstract class AbstractSQLConfig implements SQLConfig { column = logic.getKey(); Log.i(TAG, "getRegExpString column = " + column); - JSONArray arr = newJSONArray(value); + L arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } - return getRegExpString(key, column, arr.toArray(), logic.getType(), ignoreCase); + return gainRegExpString(key, column, arr.toArray(), logic.getType(), ignoreCase); } /**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 gainRegExpString(String key, String column, Object[] values, int type, boolean ignoreCase) + throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; } @@ -3376,10 +4281,11 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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)) + + gainRegExpString(key, column, (String) values[i], ignoreCase); } - return getCondition(Logic.isNot(type), condition); + return gainCondition(Logic.isNot(type), condition); } /**WHERE key REGEXP 'value' @@ -3388,24 +4294,32 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @param ignoreCase * @return key REGEXP 'value' */ - @JSONField(serialize = false) - public String getRegExpString(String key, String column, String value, boolean ignoreCase) { - if (isPostgreSQL()) { - return getKey(column) + " ~" + (ignoreCase ? "* " : " ") + getValue(key, column, value); + public String gainRegExpString(String key, String column, String value, boolean ignoreCase) { + if (isPSQL()) { + return gainKey(column) + " ~" + (ignoreCase ? "* " : " ") + gainValue(key, column, value); + } + if (isOracle() || isDameng() || isKingBase() || (isMySQL() && gainDBVersionNums()[0] >= 8)) { + return "regexp_like(" + gainKey(column) + ", " + gainValue(key, column, value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; } - if (isOracle()) { - return "regexp_like(" + getKey(column) + ", " + getValue(key, column, value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; + if (isPresto() || isTrino()) { + return "regexp_like(" + (ignoreCase ? "lower(" : "") + gainKey(column) + (ignoreCase ? ")" : "") + + ", " + (ignoreCase ? "lower(" : "") + gainValue(key, column, value) + (ignoreCase ? ")" : "") + ")"; } if (isClickHouse()) { - return "match(" + (ignoreCase ? "lower(" : "") + getKey(column) + (ignoreCase ? ")" : "") - + ", " + (ignoreCase ? "lower(" : "") + getValue(key, column, value) + (ignoreCase ? ")" : "") + ")"; + return "match(" + (ignoreCase ? "lower(" : "") + gainKey(column) + (ignoreCase ? ")" : "") + + ", " + (ignoreCase ? "lower(" : "") + gainValue(key, column, value) + (ignoreCase ? ")" : "") + ")"; + } + if (isElasticsearch()) { + return gainKey(column) + " RLIKE " + gainValue(key, column, value); } if (isHive()) { - return (ignoreCase ? "lower(" : "") + getKey(column) + (ignoreCase ? ")" : "") - + " REGEXP " + (ignoreCase ? "lower(" : "") + getValue(key, column, value) + (ignoreCase ? ")" : ""); + return (ignoreCase ? "lower(" : "") + gainKey(column) + (ignoreCase ? ")" : "") + + " REGEXP " + (ignoreCase ? "lower(" : "") + gainValue(key, column, value) + (ignoreCase ? ")" : ""); } - return getKey(column) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(key, column, value); + return gainKey(column) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + gainValue(key, column, value); } + + //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -3416,12 +4330,12 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @param key * @param value 'start,end' * @return LOGIC [ key BETWEEN 'start' AND 'end' ] - * @throws IllegalArgumentException + * @throws IllegalArgumentException */ - @JSONField(serialize = false) - public String getBetweenString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { + public String gainBetweenString(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 ""; @@ -3431,21 +4345,22 @@ public abstract class AbstractSQLConfig implements SQLConfig { column = logic.getKey(); Log.i(TAG, "getBetweenString column = " + column); - JSONArray arr = newJSONArray(value); + L arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } - return getBetweenString(key, column, arr.toArray(), logic.getType()); + return gainBetweenString(key, column, arr.toArray(), logic.getType()); } /**WHERE key BETWEEN 'start' AND 'end' * @param key - * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? + * @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 { + public String gainBetweenString(String key, String column, Object[] values, int type) throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; } @@ -3459,27 +4374,31 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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)) + + "(" + gainBetweenString(key, column, vs[0], (Object) vs[1]) + ")"; } - return getCondition(Logic.isNot(type), condition); + return gainCondition(Logic.isNot(type), condition); } /**WHERE key BETWEEN 'start' AND 'end' - * @param key - * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? - * @return key BETWEEN 'start' AND 'end' - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getBetweenString(String key, 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 的规则 !"); + * @return key + * @param column + * @param start + * @param end + * @return LOGIC [ key BETWEEN 'start' AND 'end' ] + * @throws IllegalArgumentException + */ + public String gainBetweenString(String key, String column, Object start, Object end) throws IllegalArgumentException { + if (JSON.isBoolOrNumOrStr(start) == false || JSON.isBoolOrNumOrStr(end) == false) { + throw new IllegalArgumentException(key + ":value 中 value 不合法!类型为 String 时必须包括1个逗号 , " + + "且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); } - return getKey(column) + " BETWEEN " + getValue(key, column, start) + AND + getValue(key, column, end); + return gainKey(column) + " BETWEEN " + gainValue(key, column, start) + AND + gainValue(key, column, end); } @@ -3494,10 +4413,9 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @param key * @param range "condition0,condition1..." * @return key condition0 AND key condition1 AND ... - * @throws Exception + * @throws Exception */ - @JSONField(serialize = false) - public String getRangeString(String key, String column, Object range, String rawSQL) throws Exception { + public String gainRangeString(String key, String column, Object range, String rawSQL) throws Exception { Log.i(TAG, "getRangeString column = " + column); if (range == null) {//依赖的对象都没有给出有效值,这个存在无意义。如果是客户端传的,那就能在客户端确定了。 throw new NotExistException(TAG + "getRangeString(" + column + ", " + range + ") range == null"); @@ -3518,7 +4436,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { if (logic.isNot() && l.isEmpty()) { return ""; // key!{}: [] 这个条件无效,加到 SQL 语句中 key IN() 会报错,getInString 里不好处理 } - return getKey(k) + getInString(k, column, l.toArray(), logic.isNot()); + return gainKey(k) + gainInString(k, column, l.toArray(), logic.isNot()); } throw new IllegalArgumentException(key + ":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); } @@ -3528,31 +4446,32 @@ public abstract class AbstractSQLConfig implements SQLConfig { if (rawSQL != null) { int index = rawSQL.indexOf("("); - condition = (index >= 0 && index < rawSQL.lastIndexOf(")") ? "" : getKey(k) + " ") + rawSQL; + condition = (index >= 0 && index < rawSQL.lastIndexOf(")") ? "" : gainKey(k) + " ") + rawSQL; } if (cs != null) { 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 fk = gainKey(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 +4482,10 @@ public abstract class AbstractSQLConfig implements SQLConfig { } 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,15 +4493,16 @@ public abstract class AbstractSQLConfig implements SQLConfig { condition += ((i <= 0 ? "" : lk) + expr); } } - + if (condition.isEmpty()) { return ""; } - return getCondition(logic.isNot(), condition); + return gainCondition(logic.isNot(), condition); } - else if (range instanceof Subquery) { //如果在 Parser 解析成 SQL 字符串再引用,没法保证安全性,毕竟可以再通过远程函数等方式来拼接再替代,最后引用的字符串就能注入 - return getKey(k) + (logic.isNot() ? NOT : "") + " IN " + getSubqueryString((Subquery) range); + else if (range instanceof Subquery) { + // 如果在 Parser 解析成 SQL 字符串再引用,没法保证安全性,毕竟可以再通过远程函数等方式来拼接再替代,最后引用的字符串就能注入 + return gainKey(k) + (logic.isNot() ? NOT : "") + " IN " + gainSubqueryString((Subquery) range); } throw new IllegalArgumentException(key + ":range 类型为" + range.getClass().getSimpleName() @@ -3590,14 +4511,13 @@ public abstract class AbstractSQLConfig implements SQLConfig { /**WHERE key IN ('key0', 'key1', ... ) * @param in * @return IN ('key0', 'key1', ... ) - * @throws NotExistException + * @throws NotExistException */ - @JSONField(serialize = false) - public String getInString(String key, String column, Object[] in, boolean not) throws NotExistException { + public String gainInString(String key, String column, Object[] in, boolean not) throws NotExistException { String condition = ""; if (in != null) {//返回 "" 会导致 id:[] 空值时效果和没有筛选id一样! for (int i = 0; i < in.length; i++) { - condition += ((i > 0 ? "," : "") + getValue(key, column, in[i])); + condition += ((i > 0 ? "," : "") + gainValue(key, column, in[i])); } } if (condition.isEmpty()) {//条件如果存在必须执行,不能忽略。条件为空会导致出错,又很难保证条件不为空(@:条件),所以还是这样好 @@ -3610,16 +4530,17 @@ public abstract class AbstractSQLConfig implements SQLConfig { //}{ 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 ...) * @throws NotExistException */ - @JSONField(serialize = false) - public String getExistsString(String key, String column, Object value, String rawSQL) throws Exception { + public String gainExistsString(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 ""; @@ -3633,7 +4554,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { column = logic.getKey(); Log.i(TAG, "getExistsString column = " + column); - return (logic.isNot() ? NOT : "") + " EXISTS " + getSubqueryString((Subquery) value); + return (logic.isNot() ? NOT : "") + " EXISTS " + gainSubqueryString((Subquery) value); } //}{ exists >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -3641,83 +4562,96 @@ public abstract class AbstractSQLConfig implements SQLConfig { /**WHERE key contains value * @param key * @param value - * @return {@link #getContainString(String, Object[], int)} + * @return {@link #gainContainString(String, String, Object[], int)} * @throws NotExistException */ - @JSONField(serialize = false) - public String getContainString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { + public String gainContainString(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); column = logic.getKey(); Log.i(TAG, "getContainString column = " + column); - return getContainString(key, column, newJSONArray(value).toArray(), logic.getType()); + return gainContainString(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 { + public String gainContainString(String key, String column, Object[] childs, int type) throws IllegalArgumentException { boolean not = Logic.isNot(type); String condition = ""; if (childs != null) { for (int i = 0; i < childs.length; i++) { Object c = childs[i]; if (c instanceof Collection) { - throw new IllegalArgumentException(key + ":value 中 value 类型不能为 [JSONArray, Collection] 中的任何一个 !"); + throw new IllegalArgumentException(key + ":value 中 value 类型不能为 [JSONList, Collection] 中的任何一个 !"); } Object path = ""; if (c instanceof Map) { path = ((Map) c).get("path"); if (path != null && path instanceof String == false) { - throw new IllegalArgumentException(key + ":{ path:path, value:value } 中 path 类型错误,只能是 $, $.key1, $[0].key2 等符合 SQL 中 JSON 路径的 String !"); + 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 类型" + + "不能为 [JSONMap, JSONList, 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 (isPSQL()) { + condition += (gainKey(column) + " @> " + gainValue(key, column, newJSONArray(c))); + // operator does not exist: jsonb @> character varying "[" + c + "]"); } - else if (isOracle()) { - condition += ("json_textcontains(" + getKey(column) + ", " + (StringUtil.isEmpty(path, true) ? "'$'" : getValue(key, column, path)) + ", " + getValue(key, column, c == null ? null : c.toString()) + ")"); + else if (isOracle() || isDameng() || isKingBase()) { + condition += ("json_textcontains(" + gainKey(column) + ", " + (StringUtil.isEmpty(path, true) + ? "'$'" : gainValue(key, column, path)) + ", " + gainValue(key, column, c == null ? null : c.toString()) + ")"); + } + else if (isPresto() || isTrino()) { + condition += ("json_array_contains(cast(" + gainKey(column) + " AS VARCHAR), " + + gainValue(key, column, c) + (StringUtil.isEmpty(path, true) + ? "" : ", " + gainValue(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(" + gainKey(column) + "))" + + ", " + gainValue(key, column, v) + (StringUtil.isEmpty(path, true) + ? "" : ", " + gainValue(key, column, path)) + ")"); } else { - condition += ("json_contains(" + getKey(column) + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")"); + condition += ("json_contains(" + gainKey(column) + ", " + gainValue(key, column, v) + + (StringUtil.isEmpty(path, true) ? "" : ", " + gainValue(key, column, path)) + ")"); } } } if (condition.isEmpty()) { - condition = getKey(column) + SQL.isNull(true) + OR + getLikeString(key, column, "[]"); // key = '[]' 无结果! - } + condition = gainKey(column) + SQL.isNull(true) + OR + gainLikeString(key, column, "[]"); // key = '[]' 无结果! + } else { - condition = getKey(column) + SQL.isNull(false) + AND + "(" + condition + ")"; + condition = gainKey(column) + SQL.isNull(false) + AND + "(" + condition + ")"; } } if (condition.isEmpty()) { return ""; } - return getCondition(not, condition); + return gainCondition(not, condition); } //<> contain >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -3725,19 +4659,115 @@ public abstract class AbstractSQLConfig implements SQLConfig { //key@:{} Subquery <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + public List getWithAsExprSQLList() { + return withAsExprSQLList; + } + private void clearWithAsExprListIfNeed() { + // mysql8版本以上,子查询支持with as表达式 + if(this.isMySQL() && this.gainDBVersionNums()[0] >= 8) { + this.withAsExprSQLList = new ArrayList<>(); + } + } + + @Override + public List getWithAsExprPreparedValueList() { + return this.withAsExprPreparedValueList; + } + + @Override + public AbstractSQLConfig setWithAsExprPreparedValueList(List list) { + this.withAsExprPreparedValueList = list; + return this; + } + + /** + * 只要 method != RequestMethod.POST 就都支持 with-as表达式 + * @param cfg + * @param subquery + * @return + * @throws Exception + */ + private String withAsExprSubqueryString(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 = gainAs(); + + String withAsExpreSql; + if (list != null) { + String withQuoteName = quote + subquery.gainKey() + quote; + list.add(" " + withQuoteName + as + "(" + cfg.gainSQL(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.gainSQL(isPrepared()); + // mysql 才存在这个问题, 主表和子表是一张表 + if (isWithAsEnable && isMySQL() && StringUtil.equals(getTable(), subquery.gainFrom())) { + withAsExpreSql = " SELECT * FROM (" + withAsExpreSql + ")" + as + quote + subquery.gainKey() + quote; + } + } + + return withAsExpreSql; + } + @Override - public String getSubqueryString(Subquery subquery) throws Exception { + public String gainSubqueryString(Subquery subquery) throws Exception { if (subquery == null) { return ""; } - String range = subquery.getRange(); - SQLConfig cfg = subquery.getConfig(); + String range = subquery.gainRange(); + SQLConfig cfg = subquery.gainConfig(); + // 子查询 = 主语句 datasource + if (StringUtil.equals(this.getTable(), subquery.gainFrom()) == false && cfg.hasJoin() == false) { + cfg.setDatasource(this.getDatasource()); + } cfg.setPreparedValueList(new ArrayList<>()); - String sql = (range == null || range.isEmpty() ? "" : range) + "(" + cfg.getSQL(isPrepared()) + ") "; + String withAsExprSql = withAsExprSubqueryString(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; } @@ -3751,28 +4781,28 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @param condition * @return */ - public static String getCondition(boolean not, String condition) { - return getCondition(not, condition, false); + public static String gainCondition(boolean not, String condition) { + return gainCondition(not, condition, false); } /**拼接条件 * @param not * @param condition - * @param outerBreaket + * @param addOuterBracket * @return */ - public static String getCondition(boolean not, String condition, boolean addOuterBracket) { + public static String gainCondition(boolean not, String condition, boolean addOuterBracket) { String s = not ? NOT + "(" + condition + ")" : condition; return addOuterBracket ? "( " + s + " )" : s; } /**转为JSONArray - * @param tv + * @param obj * @return */ @NotNull - public static JSONArray newJSONArray(Object obj) { - JSONArray array = new JSONArray(); + public static > L newJSONArray(Object obj) { + L array = JSON.createJSONArray(); if (obj != null) { if (obj instanceof Collection) { array.addAll((Collection) obj); @@ -3789,11 +4819,10 @@ public abstract class AbstractSQLConfig implements SQLConfig { //SET <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**获取SET * @return - * @throws Exception + * @throws Exception */ - @JSONField(serialize = false) - public String getSetString() throws Exception { - return getSetString(getMethod(), getContent(), ! isTest()); + public String gainSetString() throws Exception { + return gainSetString(getMethod(), getContent(), ! isTest()); } //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 /**获取SET @@ -3803,8 +4832,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @throws Exception *

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

*/ - @JSONField(serialize = false) - public String getSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { + public String gainSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { Set set = content == null ? null : content.keySet(); String setString = ""; @@ -3829,10 +4857,12 @@ public abstract class AbstractSQLConfig implements SQLConfig { keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 } value = entry.getValue(); - String column = getRealKey(method, key, false, true, verifyName); + String column = gainRealKey(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 ? "" : ", ") + (gainKey(column) + " = " + + (keyType == 1 ? gainAddString(key, column, value) : (keyType == 2 + ? gainRemoveString(key, column, value) : gainValue(key, column, value)) ) + ); isFirst = false; } @@ -3841,7 +4871,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { if (setString.isEmpty()) { throw new IllegalArgumentException("PUT 请求必须在Table内设置要修改的 key:value !"); } - return (isClickHouse()?" ":" SET ") + setString; + return (isClickHouse() ? " " : " SET ") + setString; } /**SET key = concat(key, 'value') @@ -3850,13 +4880,12 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @return concat(key, 'value') * @throws IllegalArgumentException */ - @JSONField(serialize = false) - public String getAddString(String key, String column, Object value) throws IllegalArgumentException { + public String gainAddString(String key, String column, Object value) throws IllegalArgumentException { if (value instanceof Number) { - return getKey(column) + " + " + value; + return gainKey(column) + " + " + value; } if (value instanceof String) { - return SQL.concat(getKey(column), (String) getValue(key, column, value)); + return SQL.concat(gainKey(column), (String) gainValue(key, column, value)); } throw new IllegalArgumentException(key + ":value 中 value 类型错误,必须是 Number,String,Array 中的任何一种!"); } @@ -3866,136 +4895,215 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @return REPLACE (key, 'value', '') * @throws IllegalArgumentException */ - @JSONField(serialize = false) - public String getRemoveString(String key, String column, Object value) throws IllegalArgumentException { + public String gainRemoveString(String key, String column, Object value) throws IllegalArgumentException { if (value instanceof Number) { - return getKey(column) + " - " + value; + return gainKey(column) + " - " + value; } if (value instanceof String) { - return SQL.replace(getKey(column), (String) getValue(key, column, value), "''");// " replace(" + column + ", '" + value + "', '') "; + return SQL.replace(gainKey(column), (String) gainValue(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)); + public String gainSQL(boolean prepared) throws Exception { + boolean isPrepared = isPrepared(); + if (isPrepared == prepared) { + return gainSQL(this); + } + + String sql = gainSQL(this.setPrepared(prepared)); + setPrepared(isPrepared); + return sql; } /** * @param config * @return - * @throws Exception + * @throws Exception */ - public static String getSQL(AbstractSQLConfig config) throws Exception { + public static , L extends List> String gainSQL(AbstractSQLConfig config) throws Exception { if (config == null) { Log.i(TAG, "getSQL config == null >> return null;"); return null; } - //TODO procedure 改为 List procedureList; behind : true; function: callFunction(); String key; ... + // 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.gainSQLSchema(); - 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(); - if (StringUtil.isNotEmpty(tablePath, true) == false) { - Log.i(TAG, "getSQL StringUtil.isNotEmpty(tablePath, true) == false >> return null;"); + String tablePath = config.gainTablePath(); + if (StringUtil.isEmpty(tablePath, true)) { + Log.i(TAG, "getSQL StringUtil.isEmpty(tablePath, true) >> return null;"); return 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(); - } + // 解决重复添加导致报错:Parameter index out of range (6 > number of parameters, which is 5) + config.setPreparedValueList(new ArrayList<>()); + RequestMethod method = config.getMethod(); + if (method == null) { + method = GET; + } + + String cSql = null; + switch (method) { + case POST: + return "INSERT INTO " + tablePath + config.gainColumnString() + " VALUES" + config.getValuesString(); + case PUT: + if(config.isClickHouse()){ + return "ALTER TABLE " + tablePath + " UPDATE" + config.gainSetString() + config.gainWhereString(true); + } + cSql = "UPDATE " + tablePath + config.gainSetString() + config.gainWhereString(true) + + (config.isMySQL() ? config.gainLimitString() : ""); + cSql = buildWithAsExprSql(config, cSql); + return cSql; + case DELETE: + if(config.isClickHouse()){ + return "ALTER TABLE " + tablePath + " DELETE" + config.gainWhereString(true); + } + cSql = "DELETE FROM " + tablePath + config.gainWhereString(true) + + (config.isMySQL() ? config.gainLimitString() : ""); // 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.gainWhereString(false) + + config.gainAs() + q + JSONResponse.KEY_COUNT + q + config.gainLimitString(); + } + + config.setPreparedValueList(new ArrayList()); + String column = config.gainColumnString(); + 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() == JSONMap.CACHE_RAM + ? "SQL_NO_CACHE " : "") + column + " FROM " + gainConditionString(tablePath, config) + ") " + config.gainLimitString(); + } - 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(); + String sql = "SELECT " + (config.getCache() == JSONMap.CACHE_RAM + ? "SQL_NO_CACHE " : "") + column + " FROM " + gainConditionString(tablePath, config); + return explain + config.gainOraclePageSQL(sql); } - - String sql = "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); - return explain + config.getOraclePageSql(sql); + + cSql = "SELECT " + (config.getCache() == JSONMap.CACHE_RAM ? "SQL_NO_CACHE " : "") + + column + " FROM " + gainConditionString(tablePath, config) + config.gainLimitString(); + cSql = buildWithAsExprSql(config, cSql); + if(config.isElasticsearch()) { // elasticSearch 不支持 explain + return cSql; + } + return explain + cSql; + } + } + + private static , L extends List> 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 || gainDBVersionNums()[0] >= 8); } /**Oracle的分页获取 - * @param config * @param sql * @return */ - protected String getOraclePageSql(String sql) { + protected String gainOraclePageSQL(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 + gainSQLAlias() + 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); - - Subquery from = config.getFrom(); + private static , L extends List> String gainConditionString( + String table, AbstractSQLConfig config) throws Exception { + Subquery from = config.getFrom(); if (from != null) { - table = config.getSubqueryString(from) + " AS " + config.getAliasWithQuote() + " "; + table = config.gainSubqueryString(from) + config.gainAs() + config.gainSQLAliasWithQuote() + " "; } + String join = config.gainJoinString(); + + String where = config.gainWhereString(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.gainGroupString(true) + config.gainHavingString(true) + + config.gainSampleString(true) + config.gainLatestString(true) + + config.gainPartitionString(true) + config.gainFillString(true) + + config.gainOrderString(true); } - else if (RequestMethod.isHeadMethod(config.getMethod(), true)) { // TODO 加参数 isPagenation 判断是 GET 内分页 query:2 查总数,不用加这些条件 - aggregation = config.getGroupString(true) + config.getHavingString(true) ; + else if (RequestMethod.isHeadMethod(config.getMethod(), true)) { + // TODO 加参数 isPagenation 判断是 GET 内分页 query:2 查总数,不用加这些条件 + aggregation = config.gainGroupString(true) + config.gainHavingString(true) + + config.gainSampleString(true) + config.gainLatestString(true) + + config.gainPartitionString(true) + config.gainFillString(true); } else if (config.getMethod() == PUT || config.getMethod() == DELETE) { - aggregation = config.getHavingString(true) ; + aggregation = config.gainHavingString(true) ; } else { aggregation = ""; } - String condition = table + config.getJoinString() + where + aggregation; + String condition = table + join + where + aggregation; ; //+ config.getLimitString(); //no need to optimize @@ -4040,23 +5148,23 @@ public abstract class AbstractSQLConfig implements SQLConfig { return keyPrefix; } @Override - public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { + public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { this.keyPrefix = keyPrefix; return this; } - public String getJoinString() throws Exception { + public String gainJoinString() throws Exception { String joinOns = ""; 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) { - onGetJoinString(j); + onGainJoinString(j); if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) continue; @@ -4065,10 +5173,11 @@ public abstract class AbstractSQLConfig implements SQLConfig { //LEFT JOIN sys.apijson_user AS User ON User.id = Moment.userId, 都是用 = ,通过relateType处理缓存 // <"INNER JOIN User ON User.id = Moment.userId", UserConfig> TODO AS 放 getSQLTable 内 - SQLConfig jc = j.getJoinConfig(); + 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.gainSQLAlias(); List onList = j.getOnList(); //如果要强制小写,则可在子类重写这个方法再 toLowerCase @@ -4084,91 +5193,108 @@ public abstract class AbstractSQLConfig implements SQLConfig { // continue; case "*": // CROSS JOIN - onGetCrossJoinString(j); + onGainCrossJoinString(j); case "<": // LEFT JOIN case ">": // RIGHT JOIN jc.setMain(true).setKeyPrefix(false); sql = ( "<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS") ) - + " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " + quote + jt + quote; + + " JOIN ( " + jc.gainSQL(isPrepared()) + " ) " + gainAs() + 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 case ")": // FOREIGN JOIN: B & ! A - sql = " INNER JOIN " + jc.getTablePath(); + sql = " INNER JOIN " + jc.gainTablePath(); + sql = concatJoinOn(sql, quote, j, jt, onList); + break; + case "~": // ASOF JOIN: B ~= A + sql = " ASOF JOIN " + jc.gainTablePath(); sql = concatJoinOn(sql, quote, j, jt, onList); break; default: + String k = jc.gainTableKey(); throw new UnsupportedOperationException( - "join:value 中 value 里的 " + jt + "/" + j.getPath() - + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" - + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" + "join:value 中 value 里的 " + k + "/" + j.getPath() + + "错误!不支持 " + k + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN, ~ ASOF ] 之外的 JOIN 类型 !" ); } - SQLConfig oc = j.getOuterConfig(); + SQLConfig oc = j.getOnConfig(); String ow = null; if (oc != null) { oc.setPrepared(isPrepared()); oc.setPreparedValueList(new ArrayList<>()); oc.setMain(false).setKeyPrefix(true); - ow = oc.getWhereString(false); + ow = oc.gainWhereString(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); + //} } return StringUtil.isEmpty(joinOns, true) ? "" : joinOns + " \n"; } - - 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.gainSQLAlias(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 +5306,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 +5316,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 +5339,38 @@ public abstract class AbstractSQLConfig implements SQLConfig { } 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 (isPSQL()) { + sql += (first ? ON : AND) + lk + (isNot ? NOT : "") + " ~" + (ignoreCase ? "* " : " ") + rk; + } + else if (isOracle() || isDameng() || isKingBase()) { + sql += (first ? ON : AND) + "regexp_like(" + lk + ", " + rk + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; } - 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 (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 +5378,13 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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()))) { + for (Join jn : joinList) { + if (tt.equals(jn.getTable()) && Objects.equals(ta, jn.getAlias())) { cast = getCast(); find = true; break; @@ -4262,49 +5392,47 @@ public abstract class AbstractSQLConfig implements SQLConfig { } 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 + "]"); - sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath - + " IS NOT NULL AND " + arrKeyPath + " @> " + itemKeyPath) + (isNot ? ") " : ""); + if (isPSQL()) { //operator does not exist: jsonb @> character varying "[" + c + "]"); + sql += (first ? ON : AND) + (isNot ? "( " : "") + gainCondition(isNot, arrKeyPath + + " IS NOT NULL AND " + arrKeyPath + " @> " + itemKeyPath) + (isNot ? ") " : ""); } - else if (isOracle()) { - sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + else if (isOracle() || isDameng() || isKingBase()) { + sql += (first ? ON : AND) + (isNot ? "( " : "") + gainCondition(isNot, arrKeyPath + " IS NOT NULL AND json_textcontains(" + arrKeyPath + ", '$', " + itemKeyPath + ")") + (isNot ? ") " : ""); } + else if (isPresto() || isTrino()) { + sql += (first ? ON : AND) + (isNot ? "( " : "") + gainCondition(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 + sql += (first ? ON : AND) + (isNot ? "( " : "") + gainCondition(isNot, arrKeyPath + " IS NOT NULL AND has(JSONExtractArrayRaw(assumeNotNull(" + arrKeyPath + "))" + ", " + itemKeyPath + ")") + (isNot ? ") " : ""); } else { - sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + sql += (first ? ON : AND) + (isNot ? "( " : "") + gainCondition(isNot, arrKeyPath + " 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 +5444,17 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 onGainJoinString(Join join) throws UnsupportedOperationException { } - protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { + protected void onGainCrossJoinString(Join join) throws UnsupportedOperationException { throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } @@ -4336,55 +5465,62 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @param isProcedure * @param callback * @return - * @throws Exception + * @throws Exception */ - public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure, Callback callback) throws Exception { + public static , L extends List> SQLConfig newSQLConfig( + RequestMethod method, String table, String alias + , M request, List> joinList, boolean isProcedure, Callback callback) throws Exception { if (request == null) { // User:{} 这种空内容在查询时也有效 - throw new NullPointerException(TAG + ": newSQLConfig request == null!"); + 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 = getBoolean(request, 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()) + "] 中的一种!"); + String database = getString(request, KEY_DATABASE); + if (StringUtil.isNotEmpty(database, false) && DATABASE_LIST.contains(database) == false) { + throw new UnsupportedDataTypeException("@database:value 中 value 错误,只能是 [" + + StringUtil.get(DATABASE_LIST.toArray()) + "] 中的一种!"); } - String schema = request.getString(KEY_SCHEMA); - String datasource = request.getString(KEY_DATASOURCE); + String datasource = getString(request, KEY_DATASOURCE); + String namespace = getString(request, KEY_NAMESPACE); + String catalog = getString(request, KEY_CATALOG); + String schema = getString(request, KEY_SCHEMA); - SQLConfig config = callback.getSQLConfig(method, database, schema, datasource, table); + SQLConfig config = (SQLConfig) 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); @@ -4392,7 +5528,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { } } if (newIdIn.isEmpty()) { - throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); + throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); } idIn = newIdIn; @@ -4406,15 +5542,15 @@ public abstract class AbstractSQLConfig implements SQLConfig { id = callback.newId(method, database, schema, datasource, table); // null 表示数据库自增 id } - if (id != null) { //null无效 - if (id instanceof Number) { - if (((Number) id).longValue() <= 0) { //一定没有值 - throw new NotExistException(TAG + ": newSQLConfig " + table + ".id <= 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)) { //一定没有值 - throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".id, true)"); + if (StringUtil.isEmpty(id, true)) { // 一定没有值 + throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".id, true)"); } } else if (id instanceof Subquery) {} @@ -4422,17 +5558,17 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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) { - throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); + if (contains == false) { // empty有效 BaseModel.isEmpty(idIn) == false) { + throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); } } @@ -4441,12 +5577,12 @@ public abstract class AbstractSQLConfig implements SQLConfig { } } - //对 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); @@ -4454,21 +5590,21 @@ public abstract class AbstractSQLConfig implements SQLConfig { } } if (newUserIdIn.isEmpty()) { - throw new NotExistException(TAG + ": newSQLConfig userIdIn instanceof List >> 去掉无效 userId 后 newIdIn.isEmpty()"); + throw new NotExistException(TAG + ": newSQLConfig userIdIn instanceof List >> 去掉无效 userId 后 newIdIn.isEmpty()"); } userIdIn = newUserIdIn; } Object userId = userIdKey.equals(idKey) ? null : request.get(userIdKey); - if (userId != null) { //null无效 - if (userId instanceof Number) { - if (((Number) userId).longValue() <= 0) { //一定没有值 - throw new NotExistException(TAG + ": newSQLConfig " + table + ".userId <= 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)) { //一定没有值 - throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".userId, true)"); + if (StringUtil.isEmpty(userId, true)) { // 一定没有值 + throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".userId, true)"); } } else if (userId instanceof Subquery) {} @@ -4476,50 +5612,57 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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) { - throw new NotExistException(TAG + ": newSQLConfig userIdIn != null && (((List) userIdIn).contains(userId) == 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); - String cache = request.getString(KEY_CACHE); - Subquery from = (Subquery) request.get(KEY_FROM); - String column = request.getString(KEY_COLUMN); - String nulls = request.getString(KEY_NULL); - String cast = request.getString(KEY_CAST); - String combine = request.getString(KEY_COMBINE); - String group = request.getString(KEY_GROUP); + String role = getString(request, KEY_ROLE); + String cache = getString(request, KEY_CACHE); + Subquery from = (Subquery) request.get(KEY_FROM); + String column = getString(request, KEY_COLUMN); + String nulls = getString(request, KEY_NULL); + String cast = getString(request, KEY_CAST); + String combine = getString(request, KEY_COMBINE); + String group = getString(request, KEY_GROUP); Object having = request.get(KEY_HAVING); - String havingAnd = request.getString(KEY_HAVING_AND); - String order = request.getString(KEY_ORDER); - String raw = request.getString(KEY_RAW); - String json = request.getString(KEY_JSON); + String havingAnd = getString(request, KEY_HAVING_AND); + String sample = getString(request, KEY_SAMPLE); + String latest = getString(request, KEY_LATEST); + String partition = getString(request, KEY_PARTITION); + String fill = getString(request, KEY_FILL); + String order = getString(request, KEY_ORDER); + Object keyMap = request.get(KEY_KEY); + String raw = getString(request, KEY_RAW); + String json = getString(request, KEY_JSON); + String mthd = getString(request, 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); @@ -4529,9 +5672,15 @@ public abstract class AbstractSQLConfig implements SQLConfig { request.remove(KEY_GROUP); request.remove(KEY_HAVING); request.remove(KEY_HAVING_AND); + request.remove(KEY_SAMPLE); + request.remove(KEY_LATEST); + request.remove(KEY_PARTITION); + request.remove(KEY_FILL); request.remove(KEY_ORDER); + request.remove(KEY_KEY); request.remove(KEY_RAW); request.remove(KEY_JSON); + request.remove(KEY_METHOD); // @null <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @@ -4542,7 +5691,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 +5709,16 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 +5730,17 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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... + + 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操作 + // 已经 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 +5748,14 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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(); @@ -4604,10 +5763,11 @@ public abstract class AbstractSQLConfig implements SQLConfig { if (values == null || values.length != columns.length) { throw new Exception("服务器内部错误:\n" + TAG - + " newSQLConfig values == null || values.length != columns.length !"); + + " 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.get(columns); //set已经判断过不为空 int idCount = id == null ? (userId == null ? 0 : 1) : (userId == null ? 1 : 2); int size = idCount + columns.length; // 以 key 数量为准 @@ -4621,33 +5781,29 @@ public abstract class AbstractSQLConfig implements SQLConfig { } for (int j = 0; j < values.length; j++) { - items.add(values[j]); // 从第 1 个开始,允许 "null" + items.add(values[j]); // 从第 1 个开始,允许 "null" } List> valuess = new ArrayList<>(1); valuess.add(items); config.setValues(valuess); } - } - else { //非POST操作 - final boolean isWhere = method != PUT; //除了POST,PUT,其它全是条件!!! + } + 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 +5825,65 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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); + 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.gainConfig(); + if (cfg != null && StringUtil.equals(table, cfg.getTable())) { + isFakeDelete = false; + } + + 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; + } + } } + } - 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 + "] 其中任何一个!"); + 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); } - int newIndex = index + key.length() + 1; - if (str.length() <= newIndex) { - break; + if (containNotDeletedValue) { // deletedKey = notDeletedValue + String key = deletedKey.toString(); + tableWhere.put(key, notDeletedValue); + andList.add(key); + whereList.add(key); } - str = str.substring(newIndex); + } + } + } + + 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 +5923,46 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 的类型为 JSONMap {} !"); } - //解决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 List ? JSON.toJSONString(value) : value); } } @@ -4780,6 +5977,28 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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<>(); @@ -4795,20 +6014,20 @@ public abstract class AbstractSQLConfig implements SQLConfig { boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); String rawColumnSQL = null; if (containColumnRaw) { - rawColumnSQL = config.getRawSQL(KEY_COLUMN, column); + rawColumnSQL = config.gainRawSQL(KEY_COLUMN, column); if (rawColumnSQL != null) { cs.add(rawColumnSQL); } } - 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); + String rawSQL = config.gainRawSQL(KEY_COLUMN, fk); if (rawSQL != null) { cs.add(rawSQL); continue; @@ -4818,8 +6037,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 +6076,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { int start = havingsStr == null ? -1 : havingsStr.indexOf("("); int end = havingsStr == null ? -1 : havingsStr.lastIndexOf(")"); if (IS_HAVING_ALLOW_NOT_FUNCTION == false && (start < 0 || start >= end)) { - throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 中的第 " + i + + throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 中的第 " + i + " 个字符 '" + havingsStr + "' 不合法!里面没有包含 SQL 函数!必须为 fun(col1,col2..)?val 格式!"); } @@ -4881,13 +6100,13 @@ public abstract class AbstractSQLConfig implements SQLConfig { } } } - else if (newHaving instanceof JSONObject) { + else if (newHaving instanceof Map) { if (isHavingAnd) { throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 类型不合法!" - + "@having&:value 中 value 只能是 String,@having:value 中 value 只能是 String 或 JSONObject !"); + + "@having&:value 中 value 只能是 String,@having:value 中 value 只能是 String 或 JSONMap !"); } - JSONObject havingObj = (JSONObject) newHaving; + M havingObj = JSON.createJSONObject((Map) newHaving); Set> havingSet = havingObj.entrySet(); for (Entry entry : havingSet) { String k = entry == null ? null : entry.getKey(); @@ -4899,6 +6118,9 @@ public abstract class AbstractSQLConfig implements SQLConfig { throw new IllegalArgumentException(table + ":{ " + havingKey + ":{ " + k + ":value } } 里的" + " value 不合法!类型只能是 String,且不允许为空!"); } + if (ignoreEmptyOrBlankStr && StringUtil.isEmpty(v, ignoreBlankStr)) { + continue; + } if (KEY_COMBINE.equals(k)) { havingCombine = (String) v; @@ -4914,12 +6136,33 @@ public abstract class AbstractSQLConfig implements SQLConfig { } else if (newHaving != null) { throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 类型不合法!" - + "@having:value 中 value 只能是 String 或 JSONObject,@having&:value 中 value 只能是 String !"); + + "@having:value 中 value 只能是 String 或 JSONMap,@having&:value 中 value 只能是 String !"); } // @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, JSONMap 中的一种!"); + } + - config.setExplain(explain); + config.setExplain(explain != null && explain); config.setCache(getCache(cache)); config.setDistinct(distinct); config.setColumn(column == null ? null : cs); //解决总是 config.column != null,总是不能得到 * @@ -4937,6 +6180,10 @@ public abstract class AbstractSQLConfig implements SQLConfig { config.setGroup(group); config.setHaving(havingMap); config.setHavingCombine(havingCombine); + config.setSample(sample); + config.setLatest(latest); + config.setPartition(partition); + config.setFill(fill); config.setOrder(order); String[] jsons = StringUtil.split(json); @@ -4959,23 +6206,75 @@ public abstract class AbstractSQLConfig implements SQLConfig { } // 关键词 - 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 (sample != null) { + request.put(KEY_SAMPLE, sample); + } + if (latest != null) { + request.put(KEY_LATEST, latest); + } + if (partition != null) { + request.put(KEY_PARTITION, partition); + } + if (fill != null) { + request.put(KEY_FILL, fill); + } + 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,11 +6290,12 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @return * @throws Exception */ - public static SQLConfig parseJoin(RequestMethod method, SQLConfig config, List joinList, Callback callback) throws Exception { + public static , L extends List> SQLConfig parseJoin( + RequestMethod method, SQLConfig config, List> joinList, Callback callback) throws Exception { boolean isQuery = RequestMethod.isQueryMethod(method); config.setKeyPrefix(isQuery && config.isMain() == false); - //TODO 解析出 SQLConfig 再合并 column, order, group 等 + //TODO 解析出 SQLConfig 再合并 column, order, group 等 if (joinList == null || joinList.isEmpty() || RequestMethod.isQueryMethod(method) == false) { return config; } @@ -5003,19 +6303,21 @@ public abstract class AbstractSQLConfig implements SQLConfig { String table; String alias; - for (Join j : joinList) { - table = j.getTable(); - alias = j.getAlias(); + for (Join join : joinList) { + table = join.getTable(); + alias = join.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, join.getRequest(), null, false, callback); + SQLConfig cacheConfig = join.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias + , join.getRequest(), null, false, callback).setCount(join.getCount()); - if (j.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置 + if (join.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 一致 @@ -5025,29 +6327,44 @@ public abstract class AbstractSQLConfig implements SQLConfig { cacheConfig.setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 } - if (isQuery) { config.setKeyPrefix(true); } 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); + if (join.getOn() != null) { + SQLConfig onConfig = newSQLConfig(method, table, alias, join.getOn(), null, false, callback); + onConfig.setMain(false) + .setKeyPrefix(true) + .setDatabase(joinConfig.getDatabase()) + .setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 + + join.setOnConfig(onConfig); + } + + if (join.getOuter() != null) { + SQLConfig outerConfig = newSQLConfig(method, table, alias, join.getOuter(), null, false, callback); + outerConfig.setMain(false) + .setKeyPrefix(true) + .setDatabase(joinConfig.getDatabase()) + .setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 + join.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(); + List onList = join.getOnList(); List column = onList == null ? null : new ArrayList<>(onList.size()); - if (column != null) { + //解决 pg 如果只查询关联键,会报找不到column的错误 + ///* SELECT count(*) AS count FROM sys.Moment AS Moment + // LEFT JOIN ( SELECT * FROM sys.Comment ) AS Comment ON Comment.momentId = Moment.id LIMIT 1 OFFSET 0 */ + if (column != null && joinConfig.isMSQL()) { // 暂时这样兼容 PostgreSQL 等不支持 SELECT 中不包含对应 key 的隐式 ON 关联字段的数据库 for (On on : onList) { - column.add(on.getKey()); + column.add(on.getKey()); // TODO PostgreSQL 等需要找到具体的 targetTable 对应 targetKey 来加到 SELECT,比直接 SELECT * 性能更好 } } @@ -5060,8 +6377,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { } } - j.setJoinConfig(joinConfig); - j.setCacheConfig(cacheConfig); + join.setJoinConfig(joinConfig); + join.setCacheConfig(cacheConfig); } config.setJoinList(joinList); @@ -5079,9 +6396,9 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @param saveLogic 保留逻辑运算符 & | ! * @return */ - public static String getRealKey(RequestMethod method, String originKey + public static String gainRealKey(RequestMethod method, String originKey , boolean isTableKey, boolean saveLogic) throws Exception { - return getRealKey(method, originKey, isTableKey, saveLogic, true); + return gainRealKey(method, originKey, isTableKey, saveLogic, true); } /**获取客户端实际需要的key * @param method @@ -5091,15 +6408,15 @@ public abstract class AbstractSQLConfig implements SQLConfig { * @param verifyName 验证key名是否符合代码变量/常量名 * @return */ - public static String getRealKey(RequestMethod method, String originKey + public static String gainRealKey(RequestMethod method, String originKey , boolean isTableKey, boolean saveLogic, boolean verifyName) throws Exception { Log.i(TAG, "getRealKey saveLogic = " + saveLogic + "; originKey = " + originKey); - if (originKey == null || apijson.JSONObject.isArrayKey(originKey)) { - Log.w(TAG, "getRealKey originKey == null || apijson.JSONObject.isArrayKey(originKey) >> return originKey;"); + if (originKey == null || JSONMap.isArrayKey(originKey)) { + Log.w(TAG, "getRealKey originKey == null || apijson.JSONMap.isArrayKey(originKey) >> return 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 +6428,15 @@ public abstract class AbstractSQLConfig implements SQLConfig { 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 +6453,16 @@ public abstract class AbstractSQLConfig implements SQLConfig { } else if (key.endsWith("{}")) {//被包含 IN,或者说key对应值处于value的范围内。查询时处理 key = key.substring(0, key.length() - 2); - } + } else if (key.endsWith("}{")) {//被包含 EXISTS,或者说key对应值处于value的范围内。查询时处理 key = key.substring(0, key.length() - 2); - } + } else if (key.endsWith("<>")) {//包含 json_contains,或者说value处于key对应值的范围内。查询时处理 key = key.substring(0, key.length() - 2); - } + } else if (key.endsWith("()")) {//方法,查询完后处理,先用一个Map保存 key = key.substring(0, key.length() - 2); - } + } else if (key.endsWith("@")) {//引用,引用对象查询完后处理。fillTarget中暂时不用处理,因为非GET请求都是由给定的id确定,不需要引用 key = key.substring(0, key.length() - 1); } @@ -5163,37 +6482,44 @@ public abstract class AbstractSQLConfig implements SQLConfig { if (method == PUT) {//不为PUT就抛异常 key = key.substring(0, key.length() - 1); } - } + } else if (key.endsWith("-")) {//缩减,PUT查询时处理 if (method == PUT) {//不为PUT就抛异常 key = key.substring(0, key.length() - 1); } } - //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; } @@ -5202,7 +6528,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { } - public static interface IdCallback { + public static interface IdCallback { /**为 post 请求新建 id, 只能是 Long 或 String * @param method * @param database @@ -5230,22 +6556,22 @@ public abstract class AbstractSQLConfig implements SQLConfig { String getUserIdKey(String database, String schema, String datasource, String table); } - public static interface Callback extends IdCallback { - /**获取 SQLConfig 的实例 + public static interface Callback, L extends List> extends IdCallback { + /**获取 SQLConfig 的实例 * @param method * @param database * @param schema * @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, M request, String combine, String item, String key) throws Exception; } public static Long LAST_ID; @@ -5253,7 +6579,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { LAST_ID = System.currentTimeMillis(); } - public static abstract class SimpleCallback implements Callback { + public static abstract class SimpleCallback, L extends List> implements Callback { @SuppressWarnings("unchecked") @Override @@ -5263,7 +6589,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { id = LAST_ID + 1; // 解决高并发下 id 冲突导致新增记录失败 } LAST_ID = id; - + return (T) id; } @@ -5278,10 +6604,37 @@ public abstract class AbstractSQLConfig implements SQLConfig { } @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!"); + public void onMissingKey4Combine(String name, M request, String combine, String item, String key) throws Exception { + 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; } + } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 1f1d4bd96a97c631e0dd42f0be24d17ccd42c4f2..7fb1bac511948b86b53c9302a141c1c76b46d735 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -1,53 +1,48 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ package apijson.orm; +import apijson.*; +import apijson.orm.Join.On; +import apijson.orm.exception.NotExistException; + 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, L extends List> + 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 +60,14 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { public int getExecutedSQLCount() { return executedSQLCount; } - + private long executedSQLDuration = 0; private long sqlResultDuration = 0; @Override public long getExecutedSQLDuration() { return executedSQLDuration; } - + @Override public long getSqlResultDuration() { return sqlResultDuration; @@ -82,56 +77,62 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { /** * 缓存 Map */ - protected Map> cacheMap = new HashMap<>(); + protected Map> cacheMap = new HashMap<>(); /**保存缓存 * @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) { - List list = getCache(sql, config); + public M getCacheItem(String sql, int position, SQLConfig config) { + List list = getCache(sql, config); + return getCacheItem(list, position, config); + } + + public M getCacheItem(List list, int position, SQLConfig config) { // 只要 list 不为 null,则如果 list.get(position) == null,则返回 {} ,避免再次 SQL 查询 if (list == null) { return null; } - JSONObject result = position >= list.size() ? null : list.get(position); - return result != null ? result : new JSONObject(); + M result = position >= list.size() ? null : list.get(position); + return result != null ? result : JSON.createJSONObject(); } + + /**移除缓存 * @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,25 +164,23 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { * @throws Exception */ @Override - public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Exception { + public M 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); + final String sql = config.gainSQL(false); if (StringUtil.isEmpty(sql, true)) { Log.e(TAG, "execute StringUtil.isEmpty(sql, true) >> return null;"); return null; } + Parser parser2 = config.gainParser(); + parser = parser2 != null ? parser2 : getParser();; + boolean isExplain = config.isExplain(); boolean isHead = RequestMethod.isHeadMethod(config.getMethod(), true); final int position = config.getPosition(); - JSONObject result; + M result; if (isExplain == false) { generatedSQLCount ++; @@ -191,17 +190,18 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { Log.d(TAG, "\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + "\n已生成 " + generatedSQLCount + " 条 SQL" + "\nexecute startTime = " + startTime - + "\ndatabase = " + StringUtil.getString(config.getDatabase()) - + "; schema = " + StringUtil.getString(config.getSchema()) + + "\ndatabase = " + StringUtil.get(config.getDatabase()) + + "; schema = " + StringUtil.get(config.getSchema()) + "; sql = \n" + sql + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); ResultSet rs = null; - List resultList = null; - Map childMap = null; + List resultList = null; + Map childMap = null; + Map keyMap = null; try { - if (unknowType) { + if (unknownType) { if (isExplain == false) { //只有 SELECT 才能 EXPLAIN executedSQLCount ++; executedSQLStartTime = System.currentTimeMillis(); @@ -212,14 +212,15 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { if (isExplain == false) { executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } - - result = new JSONObject(true); + + result = JSON.createJSONObject(); 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: @@ -237,11 +238,11 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { } // updateCount>0时收集结果。例如更新操作成功时,返回count(affected rows)、id字段 - result = AbstractParser.newSuccessResult(); // TODO 对 APIAuto 及其它现有的前端/客户端影响比较大,暂时还是返回 code 和 msg,5.0 再移除 new JSONObject(true); + result = parser.newSuccessResult(); // TODO 对 APIAuto 及其它现有的前端/客户端影响比较大,暂时还是返回 code 和 msg,5.0 再移除 JSON.createJSONObject(); //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 - + String idKey = config.getIdKey(); if (config.getId() != null) { result.put(idKey, config.getId()); @@ -249,18 +250,30 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { if (config.getIdIn() != null) { result.put(idKey + "[]", config.getIdIn()); } - + + if (method == RequestMethod.PUT || method == RequestMethod.DELETE) { + config.setMethod(RequestMethod.GET); + removeCache(config.gainSQL(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,26 +282,30 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { executedSQLCount ++; executedSQLStartTime = System.currentTimeMillis(); } - rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults + rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults if (isExplain == false) { executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } break; - default://OPTIONS, TRACE等 + default: //OPTIONS, TRACE等 Log.e(TAG, "execute sql = " + sql + " ; method = " + config.getMethod() + " >> return null;"); return null; } } - if (isExplain == false && isHead) { if (rs.next() == false) { - return AbstractParser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!")); + return parser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!")); } - result = AbstractParser.newSuccessResult(); - result.put(JSONResponse.KEY_COUNT, rs.getLong(1)); + result = parser.newSuccessResult(); + // 兼容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 abstract class AbstractSQLExecutor implements SQLExecutor { if (capacity > 100) { // 有 WHERE 条件,条件越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 Map> combine = config.getCombineMap(); - + List andList = combine == null ? null : combine.get("&"); int andCondCount = andList == null ? (config.getWhere() == null ? 0 : config.getWhere().size()) : andList.size(); - + List orList = combine == null ? null : combine.get("|"); int orCondCount = orList == null ? 0 : orList.size(); - + List notList = combine == null ? null : combine.get("!"); int notCondCount = notList == null ? 0 : notList.size(); - + // 有 GROUP BY 分组,字段越少过滤数据越多 String[] group = StringUtil.split(config.getGroup()); int groupCount = group == null ? 0 : group.length; if (groupCount > 0 && Arrays.asList(group).contains(config.getIdKey())) { groupCount = 0; } - + // 有 HAVING 聚合函数,字段越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 Map having = config.getHaving(); int havingCount = having == null ? 0 : having.size(); @@ -348,7 +365,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { } } } - + resultList = new ArrayList<>(capacity); } @@ -361,7 +378,7 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { //