Repository: Tencent/APIJSON Branch: master Commit: 7a53f3be99f0 Files: 90 Total size: 946.1 KB Directory structure: gitextract_lxki5zku/ ├── .github/ │ └── ISSUE_TEMPLATE/ │ ├── bug_report.yml │ ├── config.yml │ ├── feature_request.yml │ └── other_issues.yml ├── .gitignore ├── APIJSONORM/ │ ├── .gitignore │ ├── README.md │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── apijson/ │ ├── JSON.java │ ├── JSONCreator.java │ ├── JSONList.java │ ├── JSONMap.java │ ├── JSONParser.java │ ├── JSONRequest.java │ ├── JSONResponse.java │ ├── Log.java │ ├── MethodAccess.java │ ├── NotNull.java │ ├── RequestMethod.java │ ├── SQL.java │ ├── StringUtil.java │ ├── orm/ │ │ ├── AbstractFunctionParser.java │ │ ├── AbstractObjectParser.java │ │ ├── AbstractParser.java │ │ ├── AbstractSQLConfig.java │ │ ├── AbstractSQLExecutor.java │ │ ├── AbstractVerifier.java │ │ ├── Entry.java │ │ ├── FunctionParser.java │ │ ├── JSONRequest.java │ │ ├── Join.java │ │ ├── Logic.java │ │ ├── ObjectParser.java │ │ ├── OnParseCallback.java │ │ ├── Operation.java │ │ ├── Pair.java │ │ ├── Parser.java │ │ ├── ParserCreator.java │ │ ├── SQLConfig.java │ │ ├── SQLCreator.java │ │ ├── SQLExecutor.java │ │ ├── Subquery.java │ │ ├── Verifier.java │ │ ├── VerifierCreator.java │ │ ├── Visitor.java │ │ ├── exception/ │ │ │ ├── CommonException.java │ │ │ ├── ConditionErrorException.java │ │ │ ├── ConflictException.java │ │ │ ├── NotExistException.java │ │ │ ├── NotLoggedInException.java │ │ │ ├── OutOfRangeException.java │ │ │ ├── UnsupportedDataTypeException.java │ │ │ └── package-info.java │ │ ├── model/ │ │ │ ├── Access.java │ │ │ ├── AllColumn.java │ │ │ ├── AllColumnComment.java │ │ │ ├── AllTable.java │ │ │ ├── AllTableComment.java │ │ │ ├── Column.java │ │ │ ├── Document.java │ │ │ ├── ExtendedProperty.java │ │ │ ├── Function.java │ │ │ ├── PgAttribute.java │ │ │ ├── PgClass.java │ │ │ ├── Request.java │ │ │ ├── Script.java │ │ │ ├── SysColumn.java │ │ │ ├── SysTable.java │ │ │ ├── Table.java │ │ │ ├── TestRecord.java │ │ │ └── package-info.java │ │ ├── package-info.java │ │ └── script/ │ │ ├── JSR223ScriptExecutor.java │ │ ├── JavaScriptExecutor.java │ │ └── ScriptExecutor.java │ └── package-info.java ├── APIJSON初期构思及实现.docx ├── APIJSON初期构思及实现.pages ├── CONTRIBUTING.md ├── CONTRIBUTING_COMMIT.md ├── Document-Chinese.md ├── Document.md ├── LICENSE ├── Navigation.md ├── README-Chinese.md ├── README-extend.md ├── README.md ├── Roadmap.md ├── assets/ │ └── a └── 详细的说明文档.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ 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 " ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ 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 ================================================ FILE: .github/ISSUE_TEMPLATE/other_issues.yml ================================================ 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 " ================================================ FILE: .gitignore ================================================ .DS_Store .gradle yarn.lock *.project HELP.md target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/** !**/src/test/** ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans .sts4-cache ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr ### NetBeans ### /nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ build/ ### VS Code ### .vscode/ APIJSONORM/bin *.DS_Store ================================================ FILE: APIJSONORM/.gitignore ================================================ /target/ ================================================ FILE: APIJSONORM/README.md ================================================ # 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. ### Maven #### 1. 在 pom.xml 中添加 JitPack 仓库 #### 1. Add the JitPack repository to pom.xml ```xml jitpack.io https://jitpack.io ```
#### 2. 在 pom.xml 中添加 APIJSON 依赖 #### 2. Add the APIJSON dependency to pom.xml ```xml com.github.Tencent APIJSON LATEST ```

### Gradle #### 1. 在项目根目录 build.gradle 中最后添加 JitPack 仓库 #### 1. Add the JitPack repository in your root build.gradle at the end of repositories ```gradle allprojects { repositories { ... maven { url 'https://jitpack.io' } } } ```
#### 2. 在项目某个 module 目录(例如 `app`) build.gradle 中添加 apijson-orm 依赖 #### 2. Add the APIJSON dependency in one of your modules(such as `app`) ```gradle dependencies { 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 ================================================ FILE: APIJSONORM/pom.xml ================================================ 4.0.0 com.github.Tencent APIJSON 8.1.3 jar APIJSONORM APIJSON ORM Library UTF-8 UTF-8 1.8 UTF-8 1.8 1.8 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 ================================================ FILE: APIJSONORM/src/main/java/apijson/JSON.java ================================================ /*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.LinkedHashMap; import java.util.List; import java.util.Map; /**JSON工具类 防止解析时异常 * @author Lemon */ public class JSON { 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(); // } // //}; } // 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(); } 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 */ public static > L parseArray(Object json) { String s = toJSONString(json); if (StringUtil.isEmpty(s, true)) { return null; } try { L arr = (L) DEFAULT_JSON_PARSER.parseArray(s); return arr; } catch (Exception e) { Log.i(TAG, "parseArray catch \n" + e.getMessage()); } return null; } public static List parseArray(Object json, Class clazz) { String s = toJSONString(json); if (StringUtil.isEmpty(s, true)) { return null; } try { return DEFAULT_JSON_PARSER.parseArray(s, clazz); } catch (Exception e) { Log.i(TAG, "parseArray catch \n" + e.getMessage()); } return null; } /** * @param obj * @return */ public static String format(Object obj) { return toJSONString(obj, true); } /** * @param obj * @return */ public static String toJSONString(Object obj) { return toJSONString(obj, false); } public static String toJSONString(Object obj, boolean format) { if (obj == 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); } /**判断是否为JSONObject或JSONArray的isXxx方法名 * @param key * @return */ public static boolean isJSONType(String key) { return key != null && key.startsWith("is") && key.length() > 2 && key.contains("JSON"); } 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 */ @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; } throw new IllegalArgumentException("Value for key '" + key + "' is not a Map: " + value.getClass().getName()); } /** * 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 */ @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; } if (value instanceof List) { return (List) value; } throw new IllegalArgumentException("Value for key '" + key + "' is not a List: " + value.getClass().getName()); } /** * 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 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"); } /** * 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 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 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"); } /** * 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 Long getLong(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).longValue(); } 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"); } /** * 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 long getLongValue(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).longValue(); } 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"); } /** * 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 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"); } /** * 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 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"); } /** * 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 Double getDouble(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).doubleValue(); } if (value instanceof String) { try { return Double.parseDouble((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"); } /** * 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 double getDoubleValue(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).doubleValue(); } if (value instanceof String) { try { return Double.parseDouble((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"); } /** * 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 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; } } ================================================ FILE: APIJSONORM/src/main/java/apijson/JSONCreator.java ================================================ /*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; } } ================================================ FILE: APIJSONORM/src/main/java/apijson/JSONList.java ================================================ /*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); //} } ================================================ FILE: APIJSONORM/src/main/java/apijson/JSONMap.java ================================================ /*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ package apijson; import java.util.*; /**use this class instead of com.alibaba.fastjson.JSONMap * @author Lemon * @see #put * @see #puts * @see #putsAll */ //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 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< String KEY_ARRAY = "[]"; /**判断是否为Array的key * @param key * @return */ public static boolean isArrayKey(String key) { return key != null && key.endsWith(KEY_ARRAY); } /**判断是否为对应Table的key * @param key * @return */ public static boolean isTableKey(String key) { return StringUtil.isBigName(key); } /**判断是否为对应Table数组的 key * @param key * @return */ public static boolean isTableArray(String key) { return isArrayKey(key) && isTableKey(key.substring(0, key.length() - KEY_ARRAY.length())); } //judge >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //JSONObject内关键词 key <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< public static String KEY_ID = "id"; public static String KEY_ID_IN = KEY_ID + "{}"; public static String KEY_USER_ID = "userId"; public static String KEY_USER_ID_IN = KEY_USER_ID + "{}"; default String getIdKey() { return KEY_ID; } default String getIdInKey() { return KEY_ID_IN; } default String getUserIdKey() { return KEY_USER_ID; } default String getUserIdInKey() { return KEY_USER_ID_IN; } /**set "id":id in Table layer * @param id * @return */ default JSONMap setId(Long id) { return puts(getIdKey(), id); } /**set "id{}":[] in Table layer * @param list * @return */ default JSONMap setIdIn(List list) { return puts(getIdInKey(), list); } /**set "userId":userId in Table layer * @param id * @return */ default JSONMap setUserId(Long id) { return puts(getUserIdKey(), id); } /**set "userId{}":[] in Table layer * @param list * @return */ default JSONMap setUserIdIn(List list) { return puts(getUserIdInKey(), list); } int CACHE_ALL = 0; int CACHE_ROM = 1; int CACHE_RAM = 2; String CACHE_ALL_STRING = "ALL"; String CACHE_ROM_STRING = "ROM"; String CACHE_RAM_STRING = "RAM"; //@key关键字都放这个类 <<<<<<<<<<<<<<<<<<<<<< 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_DEFAULT = "@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"; // 把字段转为 JSON 输出 String KEY_STRING = "@string"; // 把字段转为 String 输入 String KEY_TRIM = "@trim"; // 去除首位空格等空白字符 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_STRING, KEY_TRIM, KEY_METHOD, KEY_GET, KEY_GETS, KEY_HEAD, KEY_HEADS, KEY_POST, KEY_PUT, KEY_DELETE )); //@key关键字都放这个类 >>>>>>>>>>>>>>>>>>>>>> /**set try, ignore exceptions * @param tri * @return this */ default JSONMap setTry(Boolean tri) { return puts(KEY_TRY, tri); } /**set catch * @param isCatch * @return this */ default JSONMap setCatch(String isCatch) { return puts(KEY_CATCH, isCatch); } /**set drop, data dropped will not return * @param drop * @return this */ default JSONMap setDrop(Boolean drop) { return puts(KEY_DROP, drop); } /**set if has default * @param hasDefault * @return this */ default JSONMap setDefault(Boolean hasDefault) { return puts(KEY_DEFAULT, hasDefault); } /**set role of request sender * @param role * @return this */ default JSONMap setRole(String role) { return puts(KEY_ROLE, role); } /**set database where table was puts * @param database * @return this */ default JSONMap setDatabase(String database) { return puts(KEY_DATABASE, database); } /**set datasource where table was puts * @param datasource * @return this */ 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 */ default JSONMap setExplain(Boolean explain) { return puts(KEY_EXPLAIN, explain); } /**set cache type * @param cache * @return * @see {@link #CACHE_ALL} * @see {@link #CACHE_RAM} * @see {@link #CACHE_ROM} */ default JSONMap setCache(Integer cache) { return puts(KEY_CACHE, cache); } /**set cache type * @param cache * @return * @see {@link #CACHE_ALL_STRING} * @see {@link #CACHE_RAM_STRING} * @see {@link #CACHE_ROM_STRING} */ default JSONMap setCache(String cache) { return puts(KEY_CACHE, cache); } /**set keys need to be returned * @param keys key0, key1, key2 ... * @return {@link #setColumn(String)} */ default JSONMap setColumn(String... keys) { return setColumn(StringUtil.get(keys, true)); } /**set keys need to be returned * @param keys "key0,key1,key2..." * @return */ default JSONMap setColumn(String keys) { return puts(KEY_COLUMN, keys); } /**set keys whose value is null * @param keys key0, key1, key2 ... * @return {@link #setNull(String)} */ default JSONMap setNull(String... keys) { return setNull(StringUtil.get(keys, true)); } /**set keys whose value is null * @param keys "key0,key1,key2..." * @return */ default JSONMap setNull(String keys) { return puts(KEY_NULL, keys); } /**set keys and types whose value should be cast to type, cast(value AS DATE) * @param keyTypes key0:type0, key1:type1, key2:type2 ... * @return {@link #setCast(String)} */ 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 */ default JSONMap setCast(String keyTypes) { return puts(KEY_CAST, keyTypes); } /**set combination of keys for conditions * @param keys key0,&key1,|key2,!key3 ... TODO or key0> | (key1{} & !key2)... * @return {@link #setColumn(String)} */ 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 */ default JSONMap setCombine(String keys) { return puts(KEY_COMBINE, keys); } /**set keys for group by * @param keys key0, key1, key2 ... * @return {@link #setGroup(String)} */ default JSONMap setGroup(String... keys) { return setGroup(StringUtil.get(keys, true)); } /**set keys for group by * @param keys "key0,key1,key2..." * @return */ default JSONMap setGroup(String keys) { return puts(KEY_GROUP, keys); } /**set keys for having * @param keys count(key0) > 1, sum(key1) <= 5, function2(key2) ? value2 ... * @return {@link #setHaving(String)} */ default JSONMap setHaving(String... keys) { return setHaving(StringUtil.get(keys, true)); } /**set keys for having * @param keys "key0,key1,key2..." * @return */ default JSONMap setHaving(String keys) { return setHaving(keys, false); } /**set keys for having * @param keys "key0,key1,key2..." * @return */ 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)} */ default JSONMap setOrder(String... keys) { return setOrder(StringUtil.get(keys, true)); } /**set keys for order by * @param keys "key0,key1+,key2-..." * @return */ default JSONMap setOrder(String keys) { return puts(KEY_ORDER, keys); } /**set key map * @param keyMap "name_tag:(name,tag);year:left(date,1,5)..." * @return */ default JSONMap setKey(String keyMap) { return puts(KEY_KEY, keyMap); } /**set keys to raw * @param keys "key0,key1,key2..." * @return */ default JSONMap setRaw(String keys) { return puts(KEY_RAW, keys); } /**set keys to cast to json * @param keys "key0,key1,key2..." * @return */ default JSONMap setJson(String keys) { return puts(KEY_JSON, keys); } /**set keys to cast to string * @param keys "key0,key1,key2..." * @return */ default JSONMap setString(String keys) { return puts(KEY_STRING, keys); } /**set keys to cast to string * @param keys "key0,key1,key2..." * @return */ default JSONMap setTrim(String keys) { return puts(KEY_TRIM, keys); } //JSONObject内关键词 key >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //Request <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /** * @param key * @param keys path = keys[0] + "/" + keys[1] + "/" + keys[2] + ... * @return {@link #puts(String, Object)} */ default JSONMap putsPath(String key, String... keys) { return puts(key+"@", StringUtil.get(keys, "/")); } /** * @param key * @param isNull * @return {@link #puts(String, Object)} */ default JSONMap putsNull(String key, boolean isNull) { return puts(key+"{}", SQL.isNull(isNull)); } /** * trim = false * @param key * @param isEmpty * @return {@link #putsEmpty(String, boolean, boolean)} */ default JSONMap putsEmpty(String key, boolean isEmpty) { return putsEmpty(key, isEmpty, false); } /** * @param key * @param isEmpty * @return {@link #puts(String, Object)} */ default JSONMap putsEmpty(String key, boolean isEmpty, boolean trim) { return puts(key+"{}", SQL.isEmpty(key, isEmpty, trim)); } /** * @param key * @param compare <=0, >5 ... * @return {@link #puts(String, Object)} */ 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 * @param key * @param value * @return {@link #putsSearch(String, String, int)} */ default JSONMap putsSearch(String key, String value) { return putsSearch(key, value, SQL.SEARCH_TYPE_CONTAIN_FULL); } /**设置搜索 * @param key * @param value * @param type * @return {@link #puts(String, Object)} */ default JSONMap putsSearch(String key, String value, int type) { return puts(key+"$", SQL.search(value, type)); } //Request >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /**put and return this * @param value must be annotated by {@link MethodAccess} * @return {@link #puts(String, Object)} */ default JSONMap puts(Object value) { put(value); return this; } /**put and return this * @param key * @param value * @return this */ default JSONMap puts(String key, Object value) { put(key, value); return this; } /**put and return value * @param value must be annotated by {@link MethodAccess} */ 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); } /**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 default void putAll(Map map) { Set> set = map == null ? null : map.entrySet(); if (set != null || set.isEmpty()) { return; } for (Map.Entry entry : set) { put(entry.getKey(), entry.getValue()); } } 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); //} } ================================================ FILE: APIJSONORM/src/main/java/apijson/JSONParser.java ================================================ /*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); } ================================================ FILE: APIJSONORM/src/main/java/apijson/JSONRequest.java ================================================ /*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.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 = JSON.createJSONObject(...); *
request.puts(...);//not a must *
request.toArray(...);//not a must */ 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 public static final String KEY_FORMAT = "format";//只在最外层,最外层用JSONRequest /**set "tag":tag in outermost layer * for write operations * @param tag * @return */ default JSONRequest setTag(String tag) { return puts(KEY_TAG, tag); } /**set "version":version in outermost layer * for target version of request * @param version * @return */ 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 */ default JSONRequest setFormat(Boolean format) { return puts(KEY_FORMAT, format); } //array object <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 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"; public static final String KEY_PAGE = "page"; public static final String KEY_JOIN = "join"; public static final String KEY_SUBQUERY_RANGE = "range"; public static final String KEY_SUBQUERY_FROM = "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? * @return * @see {@link #QUERY_TABLE} * @see {@link #QUERY_TOTAL} * @see {@link #QUERY_ALL} */ 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 */ 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 */ 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 */ 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} */ default JSONRequest setSubqueryRange(String range) { return puts(KEY_SUBQUERY_RANGE, range); } /**set from for Subquery * @param from * @return */ default JSONRequest setSubqueryFrom(String from) { return puts(KEY_SUBQUERY_FROM, from); } //array object >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /**create a parent JSONMap named KEY_ARRAY * @param count * @param page * @return {@link #toArray(int, int)} */ default M toArray(int count, int page) { return toArray(count, page, null); } /**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 */ default M toArray(int count, int page, String name) { return JSON.createJSONObject(StringUtil.get(name) + KEY_ARRAY, this.setCount(count).setPage(page)); } @Override default JSONRequest putsAll(Map map) { putAll(map); return this; } @Override default JSONRequest puts(Object value) { put(value); return this; } @Override 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(); } } ================================================ FILE: APIJSONORM/src/main/java/apijson/JSONResponse.java ================================================ /*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ package apijson; import java.util.*; /**parser for response * @author Lemon * @see #getObject * @see #getList * @use JSONResponse response = new JSONResponse(json); *
User user = response.getObject(User.class);//not a must *
List commenntList = response.getList("Comment[]", Comment.class);//not a must */ public interface JSONResponse, L extends List> extends JSONMap { 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; //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请求获得的信息<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< public static final int CODE_SUCCESS = 200; //成功 public static final int CODE_UNSUPPORTED_ENCODING = 400; //编码错误 public static final int CODE_ILLEGAL_ACCESS = 401; //权限错误 public static final int CODE_UNSUPPORTED_OPERATION = 403; //禁止操作 public static final int CODE_NOT_FOUND = 404; //未找到 public static final int CODE_ILLEGAL_ARGUMENT = 406; //参数错误 public static final int CODE_NOT_LOGGED_IN = 407; //未登录 public static final int CODE_TIME_OUT = 408; //超时 public static final int CODE_CONFLICT = 409; //重复,已存在 public static final int CODE_CONDITION_ERROR = 412; //条件错误,如密码错误 public static final int CODE_UNSUPPORTED_TYPE = 415; //类型错误 public static final int CODE_OUT_OF_RANGE = 416; //超出范围 public static final int CODE_NULL_POINTER = 417; //对象为空 public static final int CODE_SERVER_ERROR = 500; //服务器内部错误 public static final String MSG_SUCCEED = "success"; //成功 public static final String MSG_SERVER_ERROR = "Internal Server Error!"; //服务器内部错误 public static String KEY_OK = "ok"; public static String KEY_CODE = "code"; public static String KEY_MSG = "msg"; public static final String KEY_COUNT = "count"; public static final String KEY_TOTAL = "total"; public static final String KEY_INFO = "info"; //详细的分页信息 public static final String KEY_FIRST = "first"; //是否为首页 public static final String KEY_LAST = "last"; //是否为尾页 public static final String KEY_MAX = "max"; //最大页码 public static final String KEY_MORE = "more"; //是否有更多 /**获取状态 * @return */ default int getCode() { try { return JSON.getIntValue(this, KEY_CODE); } catch (Exception e) { //empty } return 0; } /**获取状态 * @return */ public static int getCode(Map reponse) { try { return JSON.getIntValue(reponse, KEY_CODE); } catch (Exception e) { //empty } return 0; } /**获取状态描述 * @return */ default String getMsg() { return JSON.getString(this, KEY_MSG); } /**获取状态描述 * @param response * @return */ public static String getMsg(Map response) { return response == null ? null : JSON.getString(response, KEY_MSG); } /**获取id * @return */ default long getId() { try { return JSON.getLongValue(this, getIdKey()); } catch (Exception e) { //empty } return 0; } /**获取数量 * @return */ default int getCount() { try { return JSON.getIntValue(this, KEY_COUNT); } catch (Exception e) { //empty } return 0; } /**获取总数 * @return */ default int getTotal() { try { return JSON.getIntValue(this, KEY_TOTAL); } catch (Exception e) { //empty } return 0; } /**是否成功 * @return */ default boolean isSuccess() { return isSuccess(getCode()); } /**是否成功 * @param code * @return */ public static boolean isSuccess(int code) { return code == CODE_SUCCESS; } /**是否成功 * @param response * @return */ public static boolean isSuccess(JSONResponse response) { return response != null && response.isSuccess(); } /**是否成功 * @param response * @return */ public static boolean isSuccess(Map response) { return response != null && isSuccess(JSON.getIntValue(response, KEY_CODE)); } /**校验服务端是否存在table * @return */ default boolean isExist() { return isExist(getCount()); } /**校验服务端是否存在table * @param count * @return */ public static boolean isExist(int count) { return count > 0; } /**校验服务端是否存在table * @param response * @return */ 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 */ default JSONResponse getJSONResponse(String key) { return getObject(key, JSONResponse.class); } //cannot get javaBeanDeserizer // /**获取内部的JSONResponse // * @param response // * @param key // * @return // */ // public static JSONResponse getJSONResponse(JSONRequest response, String key) { // return response == null ? null : response.getObject(key, JSONResponse.class); // } //状态信息,非GET请求获得的信息>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /** * key = clazz.getSimpleName() * @param clazz * @return */ default T getObject(Class clazz) { return getObject(clazz == null ? "" : clazz.getSimpleName(), clazz); } /** * @param key * @param clazz * @return */ default T getObject(String key, Class clazz) { return getObject(this, key, clazz); } /** * @param object * @param key * @param clazz * @return */ public static T getObject( Map object, String key, Class clazz) { return toObject(object == null ? null : JSON.get(object, formatObjectKey(key)), clazz); } /** * @param clazz * @return */ default T toObject(Class clazz) { return toObject(this, clazz); } /** * @param object * @param clazz * @return */ public static , L extends List> T toObject( Map object, Class clazz) { return JSON.parseObject(object, clazz); } /** * arrayObject = this * @param key * @return */ default List getList(String key) { return JSON.getList(this, key); } /** * key = KEY_ARRAY * @param object * @return */ public static List getList(Map object) { return JSON.getList(object, KEY_ARRAY); } /** * @param object * @param key * @param clazz * @return */ 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 */ default > L getArray() { return getArray(KEY_ARRAY); } /** * @param key * @return */ default > L getArray(String key) { return getArray(this, key); } /** * @param object * @return */ public static > L getArray(Map object) { return getArray(object, KEY_ARRAY); } /** * key = KEY_ARRAY * @param object * @param key * @return */ public static > L getArray(Map object, String key) { return object == null ? null : JSON.get(object, formatArrayKey(key)); } // /** // * @return // */ // default JSONRequest format() { // return format(this); // } /**格式化key名称 * @param object * @return */ 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; } M formatedObject = JSON.createJSONObject(); Set set = object.keySet(); if (set != null) { Object value; for (String key : set) { value = object.get(key); if (value instanceof List) {//JSONList,遍历来format内部项 formatedObject.put(formatArrayKey(key), format((L) value)); } else if (value instanceof Map) {//JSONRequest,往下一级提取 formatedObject.put(formatObjectKey(key), format((M) value)); } else {//其它Object,直接填充 formatedObject.put(formatOtherKey(key), value); } } } //太长查看不方便,不如debug Log.i(TAG, "format return formatedObject = " + JSON.toJSONString(formatedObject)); return formatedObject; } /**格式化key名称 * @param array * @return */ 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; } L formattedArray = JSON.createJSONArray(); Object value; for (int i = 0; i < array.size(); i++) { value = array.get(i); if (value instanceof List) {//JSONList,遍历来format内部项 formattedArray.add(format((L) value)); } else if (value instanceof Map) {//JSONRequest,往下一级提取 formattedArray.add(format((M) value)); } else {//其它Object,直接填充 formattedArray.add(value); } } //太长查看不方便,不如debug Log.i(TAG, "format return formattedArray = " + JSON.toJSONString(formattedArray)); return formattedArray; } /**获取表名称 * @param fullName name 或 name:alias * @return name => name; name:alias => alias */ public static String getTableName(String fullName) { //key:alias -> alias; key:alias[] -> alias[] int index = fullName == null ? -1 : fullName.indexOf(":"); return index < 0 ? fullName : fullName.substring(0, index); } /**获取变量名 * @param fullName * @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 (JSONMap.isArrayKey(fullName)) { fullName = StringUtil.addSuffix(fullName.substring(0, fullName.length() - 2), "list"); } 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, boolean, Boolean)} formatColon = false, formatAt = true, formatHyphen = true, firstCase = true */ public static String formatArrayKey(String key) { if (JSONMap.isArrayKey(key)) { key = StringUtil.addSuffix(key.substring(0, key.length() - 2), "list"); } int index = key == null ? -1 : key.indexOf(":"); if (index >= 0) { return key.substring(index + 1); //不处理自定义的 } 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, 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 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, boolean, Boolean)} formatColon = false, formatAt = true, formatHyphen = false, firstCase = false */ public static String formatOtherKey(String fullName) { 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); } /**格式化名称 * @param fullName name 或 name:alias * @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 formatUnderline, boolean formatDollar, Boolean firstCase) { if (fullName == null) { Log.w(TAG, "formatKey fullName == null >> return null;"); return null; } if (formatColon) { fullName = formatColon(fullName); } if (formatAt) { //关键词只去掉前缀,不格式化单词,例如 @a-b 返回 a-b ,最后不会调用 setter fullName = formatAt(fullName); } if (formatHyphen && fullName.contains("-")) { fullName = formatHyphen(fullName, true); } if (formatUnderline && fullName.contains("_")) { fullName = formatUnderline(fullName, true); } if (formatDollar && fullName.contains("$")) { fullName = formatDollar(fullName, true); } // 默认不格式化普通 key:value (value 不为 [], {}) 的 key return firstCase == null ? fullName : StringUtil.firstCase(fullName, firstCase); } /**"@key" => "key" * @param key * @return */ public static String formatAt(@NotNull String key) { return key.startsWith("@") ? key.substring(1) : key; } /**key:alias => alias * @param key * @return */ public static String formatColon(@NotNull String key) { int index = key.indexOf(":"); return index < 0 ? key : key.substring(index + 1); } /**A-b-cd-Efg => ABCdEfg * @param key * @return */ public static String formatHyphen(@NotNull String key) { return StringUtil.firstCase(formatHyphen(key, true), false); } /**A-b-cd-Efg => ABCdEfg * @param key * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 * @return */ public static String formatHyphen(@NotNull String key, Boolean firstCase) { return formatHyphen(key, firstCase, false); } /**A-b-cd-Efg => ABCdEfg * @param key * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 * @param otherCase 非首字符的大小写,true-大写,false-小写,null-不处理 * @return */ public static String formatHyphen(@NotNull String key, Boolean firstCase, Boolean otherCase) { return formatDivider(key, "-", firstCase, otherCase); } /**A_b_cd_Efg => ABCdEfg * @param key * @return */ public static String formatUnderline(@NotNull String key) { return StringUtil.firstCase(formatUnderline(key, true), false); } /**A_b_cd_Efg => ABCdEfg * @param key * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 * @return */ public static String formatUnderline(@NotNull String key, Boolean firstCase) { return formatUnderline(key, firstCase, false); } /**A_b_cd_Efg => ABCdEfg * @param key * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 * @param otherCase 非首字符的大小写,true-大写,false-小写,null-不处理 * @return */ public static String formatUnderline(@NotNull String key, Boolean firstCase, Boolean otherCase) { return formatDivider(key, "_", firstCase, otherCase); } /**A$b$cd$Efg => ABCdEfg * @param key * @return */ public static String formatDollar(@NotNull String key) { return StringUtil.firstCase(formatDollar(key, true), false); } /**A$b$cd$Efg => ABCdEfg * @param key * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 * @return */ public static String formatDollar(@NotNull String key, Boolean firstCase) { return formatDollar(key, firstCase, false); } /**A$b$cd$Efg => ABCdEfg * @param key * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 * @param otherCase 非首字符的大小写,true-大写,false-小写,null-不处理 * @return */ public static String formatDollar(@NotNull String key, Boolean firstCase, Boolean otherCase) { return formatDivider(key, "$", firstCase, otherCase); } /**A.b.cd.Efg => ABCdEfg * @param key * @return */ public static String formatDot(@NotNull String key) { return StringUtil.firstCase(formatDot(key, true), false); } /**A.b.cd.Efg => ABCdEfg * @param key * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 * @return */ public static String formatDot(@NotNull String key, Boolean firstCase) { return formatDot(key, firstCase, false); } /**A.b.cd.Efg => ABCdEfg * @param key * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 * @param otherCase 非首字符的大小写,true-大写,false-小写,null-不处理 * @return */ public static String formatDot(@NotNull String key, Boolean firstCase, Boolean otherCase) { return formatDivider(key, ".", firstCase, otherCase); } /**A/b/cd/Efg => ABCdEfg * @param key * @return */ public static String formatDivider(@NotNull String key, Boolean firstCase) { return formatDivider(key, "/", firstCase); } /**去除分割符,返回驼峰格式 * @param key * @param divider * @return */ public static String formatDivider(@NotNull String key, @NotNull String divider) { return StringUtil.firstCase(formatDivider(key, divider, true), false); } /**去除分割符,返回驼峰格式 * @param key * @param divider * @param firstCase 首字符的大小写,true-大写,false-小写,null-不处理 * @return */ public static String formatDivider(@NotNull String key, @NotNull String divider, Boolean firstCase) { return formatDivider(key, divider, firstCase, false); } /**去除分割符,返回驼峰格式 * @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.toString(); } } ================================================ FILE: APIJSONORM/src/main/java/apijson/Log.java ================================================ /*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ package apijson; import java.text.SimpleDateFormat; /**测试用日志 * @modifier Lemon */ public class Log { public static boolean DEBUG = false; public static final String LEVEL_VERBOSE = "VERBOSE"; public static final String LEVEL_INFO = "INFO"; public static final String LEVEL_DEBUG = "DEBUG"; public static final String LEVEL_WARN = "WARN"; public static final String LEVEL_ERROR = "ERROR"; public static String LEVEL = LEVEL_WARN; public static final String VERSION = "8.1.5"; 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 DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); /** * modify date format * @param dateFormatString */ public static void setDateFormat(String dateFormatString) { DATE_FORMAT = new SimpleDateFormat(dateFormatString); } /** * log info by level tag and msg * @param TAG * @param msg * @param level */ public static void logInfo(String TAG, String msg, String level) { if (level == null || level.isEmpty()) { level = LEVEL; } if (level.equals(LEVEL_VERBOSE) || level.equals(LEVEL_INFO)) { System.out.println(DATE_FORMAT.format(System.currentTimeMillis()) + ": " + TAG + "." + level + ": " + msg); } else if (level.equals(LEVEL_DEBUG) || level.equals(LEVEL_ERROR) || level.equals(LEVEL_WARN)) { System.err.println(DATE_FORMAT.format(System.currentTimeMillis()) + ": " + TAG + "." + level + ": " + msg); } } /** * @param TAG * @param msg */ public static void d(String TAG, String msg) { if (DEBUG) { logInfo(TAG, msg, LEVEL_DEBUG); } } /** * Forced debug * @param TAG tag * @param msg debug messages */ public static void fd(String TAG, String msg) { logInfo(TAG, msg, LEVEL_DEBUG); } /** * Generate separation line * @param pre prefix * @param symbol used for generating separation line * @param post postfix */ public static void sl(String pre, char symbol, String post) { System.err.println(pre + new String(new char[48]).replace('\u0000', symbol) + post); } /** * @param TAG * @param msg */ public static void v(String TAG, String msg) { if (DEBUG) { logInfo(TAG, msg, LEVEL_VERBOSE); } } /** * @param TAG * @param msg */ public static void i(String TAG, String msg) { if (DEBUG) { logInfo(TAG, msg, LEVEL_INFO); } } /** * @param TAG * @param msg */ public static void e(String TAG, String msg) { if (DEBUG) { logInfo(TAG, msg, LEVEL_ERROR); } } /** * @param TAG * @param msg */ public static void e(String TAG, String msg, Throwable e) { if (DEBUG) { if (e != null) { e.printStackTrace(); } logInfo(TAG, msg, LEVEL_ERROR); } } /** * @param TAG * @param msg */ public static void w(String TAG, String msg) { if (DEBUG) { logInfo(TAG, msg, LEVEL_WARN); } } } ================================================ FILE: APIJSONORM/src/main/java/apijson/MethodAccess.java ================================================ /*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ package apijson; import java.lang.annotation.Documented; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static apijson.orm.AbstractVerifier.ADMIN; import static apijson.orm.AbstractVerifier.CIRCLE; import static apijson.orm.AbstractVerifier.CONTACT; import static apijson.orm.AbstractVerifier.LOGIN; import static apijson.orm.AbstractVerifier.OWNER; import static apijson.orm.AbstractVerifier.UNKNOWN; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; /**请求方法权限,只允许某些角色通过对应方法访问 * @author Lemon */ @Documented @Retention(RUNTIME) @Target(TYPE) @Inherited public @interface MethodAccess { /**@see {@link RequestMethod#GET} * @return 该请求方法允许的角色 default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; */ String[] GET() default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; /**@see {@link RequestMethod#HEAD} * @return 该请求方法允许的角色 default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; */ String[] HEAD() default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; /**@see {@link RequestMethod#GETS} * @return 该请求方法允许的角色 default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; */ String[] GETS() default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; /**@see {@link RequestMethod#HEADS} * @return 该请求方法允许的角色 default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; */ String[] HEADS() default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; /**@see {@link RequestMethod#POST} * @return 该请求方法允许的角色 default {LOGIN, ADMIN}; */ String[] POST() default {OWNER, ADMIN}; /**@see {@link RequestMethod#PUT} * @return 该请求方法允许的角色 default {OWNER, ADMIN}; */ String[] PUT() default {OWNER, ADMIN}; /**@see {@link RequestMethod#DELETE} * @return 该请求方法允许的角色 default {OWNER, ADMIN}; */ String[] DELETE() default {OWNER, ADMIN}; } ================================================ FILE: APIJSONORM/src/main/java/apijson/NotNull.java ================================================ /*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ package apijson; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; /**非null注解 * javax.validation.constraints.NotNull不在JDK里面,为了减少第三方库引用就在这里实现了一个替代品 * @author Lemon */ @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) @Documented public @interface NotNull { } ================================================ FILE: APIJSONORM/src/main/java/apijson/RequestMethod.java ================================================ /*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 */ public enum RequestMethod { /** * 常规获取数据方式 */ GET, /** * 检查,默认是非空检查,返回数据总数 */ HEAD, /**Safe, Single, Simple *
限制性GET,通过POST来GET数据,不显示请求内容和返回结果,并且校验请求,一般用于对安全要求比较高的请求 */ GETS, /**Safe, Single, Simple *
限制性HEAD,通过POST来HEAD数据,不显示请求内容和返回结果,并且校验请求,一般用于对安全要求比较高的请求 */ HEADS, /** * 新增(或者说插入)数据 */ POST, /** * 修改数据,只修改传入字段对应的值 */ PUT, /** * 删除数据 */ 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 * @return */ public static boolean isGetMethod(RequestMethod method, boolean containPrivate) { return method == null || method == GET || (containPrivate && method == GETS); } /**是否为HEAD请求方法 * @param method * @param containPrivate 包含私密(非明文)获取方法HEADS * @return */ public static boolean isHeadMethod(RequestMethod method, boolean containPrivate) { return method == HEAD || (containPrivate && method == HEADS); } /**是否为查询的请求方法 * @param method * @return 读操作(GET型或HEAD型) - true, 写操作(POST,PUT,DELETE) - false */ 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 * @return */ public static boolean isPublicMethod(RequestMethod method) { return method == null || method == GET || method == HEAD; } /**是否为私有(限制请求的结构或内容)的请求方法 * @param method * @return */ public static boolean isPrivateMethod(RequestMethod method) { return ! isPublicMethod(method); } public static String getName(RequestMethod method) { return method == null ? GET.name() : method.name(); } } ================================================ FILE: APIJSONORM/src/main/java/apijson/SQL.java ================================================ /*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ package apijson; /**SQL语句,函数名尽量和JDK中相同或类似功能的函数的名称一致 * @author Lemon */ public class SQL { public static final String JOIN = " JOIN "; public static final String ON = " ON "; public static final String OR = " OR "; public static final String AND = " AND "; public static final String NOT = " NOT "; public static final String AS = " AS "; public static final String IS = " IS "; public static final String NULL = " NULL "; public static final String IS_NOT = " IS NOT "; public static final String IS_NULL = " IS NULL "; public static final String IS_NOT_NULL = " IS NOT NULL "; //括号必须紧跟函数名! count (...) 报错! public static final String COUNT = "count"; public static final String SUM = "sum"; public static final String MAX = "max"; public static final String MIN = "min"; public static final String AVG = "avg"; /** * isNull = true * @return {@link #isNull(boolean)} */ public static String isNull() { return isNull(true); } /** * @param isNull * @return {@link #IS} + (isNull ? "" : {@link #NOT}) + {@link #NULL}; */ public static String isNull(boolean isNull) { return IS + (isNull ? "" : NOT) + NULL; } /** * isNull = true * @param s * @return {@link #isNull(String, boolean)} */ public static String isNull(String s) { return isNull(s, true); } /** * @param s * @param isNull * @return s + {@link #isNull(boolean)} */ public static String isNull(String s, boolean isNull) { return s + isNull(isNull); } /** * isEmpty = true * @param s * @return {@link #isEmpty(String, boolean)} */ public static String isEmpty(String s) { return isEmpty(s, true); } /** * trim = false * @param s * @param isEmpty * @return {@link #isEmpty(String, boolean, boolean)} */ public static String isEmpty(String s, boolean isEmpty) { return isEmpty(s, isEmpty, false); } /** * nullable = true * @param s * @param isEmpty <=0 * @param trim s = trim(s); * @return {@link #isEmpty(String, boolean, boolean, boolean)} */ public static String isEmpty(String s, boolean isEmpty, boolean trim) { return isEmpty(s, isEmpty, trim, true); } /** * @param s * @param isEmpty <=0 * @param trim s = trim(s); * @param nullable isNull(s, true) + {@link #OR} + * @return {@link #lengthCompare(String, String)} */ public static String isEmpty(String s, boolean isEmpty, boolean trim, boolean nullable) { if (trim) { s = trim(s); } return (nullable ? isNull(s, true) + OR : "") + lengthCompare(s, (isEmpty ? "<=" : ">") + "0"); } /** * @param s 因为POWER(x,y)等函数含有不只一个key,所以需要客户端添加进去,服务端检测到条件中有'('和')'时就不转换,直接当SQL语句查询 * @return {@link #length(String)} + compare */ public static String lengthCompare(String s, String compare) { return length(s) + compare; } /** * @param s 因为POWER(x,y)等函数含有不只一个key,所以需要客户端添加进去,服务端检测到条件中有'('和')'时就不转换,直接当SQL语句查询 * @return "length(" + s + ")" */ 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 + ")" */ public static String charLength(String s) { return "char_length(" + s + ")"; } /** * @param s * @return "trim(" + s + ")" */ public static String trim(String s) { return "trim(" + s + ")"; } /** * @param s * @return "ltrim(" + s + ")" */ public static String trimLeft(String s) { return "ltrim(" + s + ")"; } /** * @param s * @return "rtrim(" + s + ")" */ public static String trimRight(String s) { return "rtrim(" + s + ")"; } /** * @param s * @param n * @return "left(" + s + "," + n + ")" */ public static String left(String s, int n) { return "left(" + s + "," + n + ")"; } /** * @param s * @param n * @return "right(" + s + "," + n + ")" */ public static String right(String s, int n) { return "right(" + s + "," + n + ")"; } /** * @param s * @param start * @param end * @return "substring(" + s + "," + start + "," + (end-start) + ")" */ public static String subString(String s, int start, int end) { return "substring(" + s + "," + start + "," + (end-start) + ")"; } /** * @param s * @param c * @return "instr(" + s + ", " + c + ")" */ public static String indexOf(String s, String c) { return "instr(" + s + ", " + c + ")"; } /** * @param s * @param c1 * @param c2 * @return "replace(" + s + ", " + c1 + ", " + c2 + ")" */ public static String replace(String s, String c1, String c2) { return "replace(" + s + ", " + c1 + ", " + c2 + ")"; } /** * @param s1 * @param s2 * @return "concat(" + s1 + ", " + s2 + ")" */ public static String concat(String s1, String s2) { return "concat(" + s1 + ", " + s2 + ")"; } /** * @param s1 * @param s2 * @return "strcmp(" + s1 + ", " + s2 + ")" */ public static String equals(String s1, String s2) { return "strcmp(" + s1 + ", " + s2 + ")"; } /** * @param s * @return "upper(" + s + ")" */ public static String toUpperCase(String s) { return "upper(" + s + ")"; } /** * @param s * @return "lower(" + s + ")" */ public static String toLowerCase(String s) { return "lower(" + s + ")"; } //column and function<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**字段 * @param column * @return column.isEmpty() ? "*" : column; */ public static String column(String column) { column = StringUtil.trim(column); return column.isEmpty() ? "*" : column; } /**有别名的字段 * @param column * @return {@link #count(String)} + {@link #AS}; */ public static String columnAs(String column) { return count(column) + AS; } /**函数 * @param column if (StringUtil.isEmpty(column, true) || column.contains(",")) -> column = null; * @return " " + fun + "(" + {@link #column(String)} + ") "; */ public static String function(String fun, String column) { // 支持 fun(col1,col2..) // if (StringUtil.isEmpty(column, true) || column.contains(",")) { // column = null; //解决 count(id,name) 这种多个字段导致的SQL异常 // } return " " + fun + "(" + column(column) + ") "; } /**有别名的函数 * @param column * @return {@link #function(String, String)} + {@link #AS} + fun; */ public static String functionAs(String fun, String column) { return function(fun, column) + AS + fun + " "; } /**计数 * column = null * @return {@link #count(String)} */ public static String count() { return count(null); } /**计数 * fun = {@link #COUNT} * @param column * @return {@link #functionAs(String, String)} */ public static String count(String column) { return functionAs(COUNT, column); } /**求和 * fun = {@link #SUM} * @param column * @return {@link #functionAs(String, String)} */ public static String sum(String column) { return functionAs(SUM, column); } /**最大值 * fun = {@link #MAX} * @param column * @return {@link #functionAs(String, String)} */ public static String max(String column) { return functionAs(MAX, column); } /**最小值 * fun = {@link #MIN} * @param column * @return {@link #functionAs(String, String)} */ public static String min(String column) { return functionAs(MIN, column); } /**平均值 * fun = {@link #AVG} * @param column * @return {@link #functionAs(String, String)} */ public static String avg(String column) { return functionAs(AVG, column); } //column and function>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //search<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< public static final int SEARCH_TYPE_CONTAIN_FULL = 0; public static final int SEARCH_TYPE_CONTAIN_ORDER = 1; public static final int SEARCH_TYPE_CONTAIN_SINGLE = 2; public static final int SEARCH_TYPE_CONTAIN_ANY = 3; public static final int SEARCH_TYPE_START = 4; public static final int SEARCH_TYPE_END = 5; public static final int SEARCH_TYPE_START_SINGLE = 6; public static final int SEARCH_TYPE_END_SINGLE = 7; public static final int SEARCH_TYPE_PART_MATCH = 8; /**获取搜索值 * @param s * @return */ public static String search(String s) { return search(s, SEARCH_TYPE_CONTAIN_FULL); } /**获取搜索值 * @param s * @param type * @return */ public static String search(String s, int type) { return search(s, type, true); } /**获取搜索值 * @param s * @param type * @param ignoreCase * @return default SEARCH_TYPE_CONTAIN_FULL */ public static String search(String s, int type, boolean ignoreCase) { if (s == null) { return null; } switch (type) { case SEARCH_TYPE_CONTAIN_SINGLE: return "_" + s + "_"; case SEARCH_TYPE_CONTAIN_ORDER: char[] cs = s.toCharArray(); if (cs == null) { return null; } String value = "%"; for (int i = 0; i < cs.length; i++) { value += cs[i] + "%"; } return value; case SEARCH_TYPE_START: return s + "%"; case SEARCH_TYPE_END: return "%" + s; case SEARCH_TYPE_START_SINGLE: return s + "_"; case SEARCH_TYPE_END_SINGLE: return "_" + s; case SEARCH_TYPE_CONTAIN_ANY: case SEARCH_TYPE_PART_MATCH: cs = s.toCharArray(); if (cs == null) { return null; } value = ""; for (int i = 0; i < cs.length; i++) { value += search("" + cs[i], SEARCH_TYPE_CONTAIN_FULL, ignoreCase); } return value; default://SEARCH_TYPE_CONTAIN_FULL return "%" + s + "%"; } } //search>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> public static boolean isBooleanOrNumber(String type) { type = StringUtil.toUpperCase(type, true); return type.isEmpty() || (type.endsWith("INT") && type.endsWith("POINT") == false) || type.endsWith("BOOLEAN") || type.endsWith("ENUM") || type.endsWith("FLOAT") || type.endsWith("DOUBLE") || type.endsWith("DECIMAL"); } } ================================================ FILE: APIJSONORM/src/main/java/apijson/StringUtil.java ================================================ /*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ package apijson; import java.io.File; import java.math.BigDecimal; import java.text.DecimalFormat; import java.util.Objects; import java.util.regex.Pattern; /**通用字符串(String)相关类,为null时返回"" * @author Lemon * @use StringUtil. */ public class StringUtil { private static final String TAG = "StringUtil"; public StringUtil() { } public static final String UTF_8 = "utf-8"; public static final String EMPTY = "无"; public static final String UNKNOWN = "未知"; public static final String UNLIMITED = "不限"; public static final String I = "我"; public static final String YOU = "你"; public static final String HE = "他"; public static final String SHE = "她"; public static final String IT = "它"; public static final String MALE = "男"; public static final String FEMALE = "女"; public static final String TODO = "未完成"; public static final String DONE = "已完成"; public static final String FAIL = "失败"; public static final String SUCCESS = "成功"; public static final String SUNDAY = "日"; public static final String MONDAY = "一"; public static final String TUESDAY = "二"; public static final String WEDNESDAY = "三"; public static final String THURSDAY = "四"; public static final String FRIDAY = "五"; public static final String SATURDAY = "六"; public static final String YUAN = "元"; 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 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(); } /**FIXME 用 get 替代 * @param cs * @return */ @Deprecated public static String getString(CharSequence cs) { return cs == null ? "" : cs.toString(); } /**FIXME 用 get 替代 * @param s * @return */ @Deprecated public static String getString(String s) { return s == null ? "" : s; } /**FIXME 用 get 替代 * ignoreEmptyItem = false; * split = "," * @param array * @return {@link #get(Object[], boolean)} */ @Deprecated public static String getString(Object[] array) { return get(array, false); } /**FIXME 用 get 替代 * split = "," * @param array * @param ignoreEmptyItem * @return {@link #get(Object[], boolean)} */ @Deprecated public static String getString(Object[] array, boolean ignoreEmptyItem) { return get(array, null, ignoreEmptyItem); } /**FIXME 用 get 替代 * ignoreEmptyItem = false; * @param array * @param split * @return {@link #get(Object[], String, boolean)} */ @Deprecated public static String getString(Object[] array, String split) { return get(array, split, false); } //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/182 /**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 #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) { 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 trim(object); } /**FIXME 用 trim 替代 * @param cs * @return */ @Deprecated public static String getTrimedString(CharSequence cs) { return trim(cs); } /**FIXME 用 trim 替代 * @param s * @return */ @Deprecated public static String getTrimedString(String s) { 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 noBlank(object); } /**FIXME 用 noBlank 替代 * @param cs * @return */ @Deprecated public static String getNoBlankString(CharSequence cs) { return noBlank(cs); } /**FIXME 用 noBlank 替代 * @param s * @return */ @Deprecated public static String getNoBlankString(String s) { return noBlank(s); } //获取去掉所有空格后的string >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //获取string的长度<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**获取string的长度,为null则返回0 * @param object * @param trim * @return */ public static int length(Object object, boolean trim) { return length(get(object), trim); } /**获取string的长度,为null则返回0 * @param cs * @param trim * @return */ 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) { return length(s, trim); } //获取string的长度>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //判断字符是否为空 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**判断字符是否为空 trim = true * @param obj * @return */ public static boolean isEmpty(Object obj) { return isEmpty(obj, true); } /**判断字符是否为空 * @param obj * @param trim * @return */ public static boolean isEmpty(Object obj, boolean trim) { return isEmpty(get(obj), trim); } /**判断字符是否为空 trim = true * @param cs * @return */ public static boolean isEmpty(CharSequence cs) { return isEmpty(cs, true); } /**判断字符是否为空 * @param cs * @param trim * @return */ public static boolean isEmpty(CharSequence cs, boolean trim) { return isEmpty(get(cs), trim); } /**判断字符是否为空 trim = true * @param s * @return */ public static boolean isEmpty(String s) { return isEmpty(s, true); } /**判断字符是否为空 * @param s * @param trim * @return */ public static boolean isEmpty(String s, boolean trim) { // Log.i(TAG, "isEmpty s = " + s); if (s == null) { return true; } if (trim) { s = s.trim(); } if (s.isEmpty()) { return true; } current = s; return false; } //判断字符是否为空 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //判断字符是否非空 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**判断字符是否非空 trim = true * @param obj * @return */ public static boolean isNotEmpty(Object obj) { return ! isEmpty(obj); } /**判断字符是否非空 * @param obj * @param trim * @return */ public static boolean isNotEmpty(Object obj, boolean trim) { return ! isEmpty(obj, trim); } /**判断字符是否非空 trim = true * @param cs * @return */ public static boolean isNotEmpty(CharSequence cs) { return ! isEmpty(cs); } /**判断字符是否非空 * @param cs * @param trim * @return */ public static boolean isNotEmpty(CharSequence cs, boolean trim) { return ! isEmpty(cs, trim); } /**判断字符是否非空 trim = true * @param s * @return */ public static boolean isNotEmpty(String s) { return ! isEmpty(s); } /**判断字符是否非空 * @param s * @param trim * @return */ public static boolean isNotEmpty(String s, boolean trim) { return ! isEmpty(s, trim); } //判断字符是否非空 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //判断字符类型 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< public static final Pattern PATTERN_NUMBER; 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; public static final Pattern PATTERN_ALPHA_BIG; public static final Pattern PATTERN_ALPHA_SMALL; public static final Pattern PATTERN_BRANCH_URL; static { PATTERN_NUMBER = Pattern.compile("^[0-9]+$"); PATTERN_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个中英字符测试通过 //newest phone regex expression reference https://github.com/VincentSit/ChinaMobilePhoneNumberRegex PATTERN_PHONE = Pattern.compile("^1(?:3\\d{3}|5[^4\\D]\\d{2}|8\\d{3}|7(?:[0-35-9]\\d{2}|4(?:0\\d|1[0-2]|9\\d))|9[0-35-9]\\d{2}|6[2567]\\d{2}|4(?:(?:10|4[01])\\d{3}|[68]\\d{4}|[579]\\d{2}))\\d{6}$"); PATTERN_EMAIL = Pattern.compile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$"); PATTERN_ID_CARD = Pattern.compile("(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{2}$)"); PATTERN_PASSWORD = Pattern.compile("^[0-9a-zA-Z]+$"); PATTERN_BRANCH_URL = Pattern.compile("^[0-9a-zA-Z-_/]+$"); } /**判断手机格式是否正确 * @param phone * @return */ public static boolean isPhone(String phone) { if (isNotEmpty(phone, true) == false) { return false; } current = phone; return PATTERN_PHONE.matcher(phone).matches(); } /**判断手机格式是否正确 * @param s * @return */ public static boolean isPassword(String s) { return length(s, false) >= 6 && PATTERN_PASSWORD.matcher(s).matches(); } /**判断是否全是数字密码 * @param s * @return */ public static boolean isNumberPassword(String s) { return length(s, false) == 6 && isNumber(s); } /**判断email格式是否正确 * @param email * @return */ public static boolean isEmail(String email) { if (isEmpty(email, true)) { return false; } current = email; return PATTERN_EMAIL.matcher(email).matches(); } /**判断是否全是验证码 * @param s * @return */ public static boolean isVerify(String s) { return length(s, false) >= 4 && isNumber(s); } /**判断是否全是数字 * @param s * @return */ public static boolean isNumber(String s) { if (isEmpty(s, true)) { return false; } current = s; return PATTERN_NUMBER.matcher(s).matches(); } /**判断是否全是字母 * @param s * @return */ public static boolean isAlpha(String s) { if (isEmpty(s, true)) { return false; } current = s; return PATTERN_ALPHA.matcher(s).matches(); } /**判断是否全是数字或字母 * @param s * @return */ public static boolean isNumberOrAlpha(String 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(); } /**判断是否为代码名称,只能包含字母,数字或下划线 * @param s * @return */ public static boolean isName(String s) { if (s == null || s.isEmpty()) { return false; } String first = s.substring(0, 1); if ("_".equals(first) == false && PATTERN_ALPHA.matcher(first).matches() == false) { return false; } return s.length() <= 1 ? true : PATTERN_NAME.matcher(s.substring(1)).matches(); } /**判断是否为首字母大写的代码名称 * @param s * @return */ public static boolean isBigName(String s) { if (s == null || s.isEmpty() || PATTERN_ALPHA_BIG.matcher(s.substring(0, 1)).matches() == false) { return false; } return s.length() <= 1 ? true : PATTERN_NAME.matcher(s.substring(1)).matches(); } /**判断是否为首字母小写的代码名称 * @param s * @return */ public static boolean isSmallName(String s) { if (s == null || s.isEmpty() || PATTERN_ALPHA_SMALL.matcher(s.substring(0, 1)).matches() == false) { return false; } return s.length() <= 1 ? true : PATTERN_NAME.matcher(s.substring(1)).matches(); } /**判断字符类型是否是身份证号 * @param number * @return */ public static boolean isIDCard(String number) { if (isCombineOfNumOrAlpha(number) == false) { return false; } number = get(number); if (number.length() == 15) { Log.i(TAG, "isIDCard number.length() == 15 old IDCard"); current = number; return true; } if (number.length() == 18) { current = number; return true; } return false; } public static final String HTTP = "http"; public static final String URL_PREFIX = "http://"; public static final String URL_PREFIXs = "https://"; /**判断字符类型是否是网址 * @param url * @return */ public static boolean isUrl(String url) { if (isNotEmpty(url, true) == false) { return false; } if (! url.startsWith(URL_PREFIX) && ! url.startsWith(URL_PREFIXs)) { return false; } current = url; return true; } public static boolean isBranchUrl(String branchUrl) { if (isEmpty(branchUrl, false)) { return false; } return PATTERN_BRANCH_URL.matcher(branchUrl).matches(); } public static final String FILE_PATH_PREFIX = "file://"; /**判断文件路径是否存在 * @param path * @return */ public static boolean isFilePathExist(String path) { return StringUtil.isFilePath(path) && new File(path).exists(); } public static final String SEPARATOR = "/"; /**判断是否为路径 * @param path * @return */ public static boolean isPath(String path) { return StringUtil.isNotEmpty(path, true) && path.contains(SEPARATOR) && path.contains(SEPARATOR + SEPARATOR) == false && path.endsWith(SEPARATOR) == false; } /**判断字符类型是否是路径 * @param path * @return */ public static boolean isFilePath(String path) { if (isNotEmpty(path, true) == false) { return false; } if (! path.contains(".") || path.endsWith(".")) { return false; } current = path; return true; } //判断字符类型 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //提取特殊字符<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**去掉string内所有非数字类型字符 * @param object * @return */ public static String getNumber(Object object) { return getNumber(get(object)); } /**去掉string内所有非数字类型字符 * @param cs * @return */ public static String getNumber(CharSequence cs) { return getNumber(get(cs)); } /**去掉string内所有非数字类型字符 * @param s * @return */ public static String getNumber(String s) { return getNumber(s, false); } //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/182 /**去掉string内所有非数字类型字符 * @param s -string passed in * @param onlyStart 中间有非数字时只获取前面的数字 * @return limit String *

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

*/ public static String getNumber(String s, boolean onlyStart) { if (isEmpty(s, true)) { return ""; } StringBuilder numberString = new StringBuilder(""); String single; for (int i = 0; i < s.length(); i++) { single = s.substring(i, i + 1); if (isNumber(single)) { numberString.append(single); } else { if (onlyStart) { return numberString.toString(); } } } return numberString.toString(); } //提取特殊字符>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //校正(自动补全等)字符串<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**获取网址,自动补全 * @param url * @return */ public static String getCorrectUrl(String url) { Log.i(TAG, "getCorrectUrl : \n" + url); if (isNotEmpty(url, true) == false) { return ""; } // if (! url.endsWith("/") && ! url.endsWith(".html")) { // url = url + "/"; // } if (isUrl(url) == false) { return URL_PREFIX + url; } return url; } /**获取去掉所有 空格 、"-" 、"+86" 后的phone * @param phone * @return */ public static String getCorrectPhone(String phone) { if (isNotEmpty(phone, true) == false) { return ""; } phone = noBlank(phone); phone = phone.replaceAll("-", ""); if (phone.startsWith("+86")) { phone = phone.substring(3); } return phone; } /**获取邮箱,自动补全 * @param email * @return */ public static String getCorrectEmail(String email) { if (isNotEmpty(email, true) == false) { return ""; } email = noBlank(email); if (isEmail(email) == false && ! email.endsWith(".com")) { email += ".com"; } return email; } public static final int PRICE_FORMAT_DEFAULT = 0; public static final int PRICE_FORMAT_PREFIX = 1; public static final int PRICE_FORMAT_SUFFIX = 2; public static final int PRICE_FORMAT_PREFIX_WITH_BLANK = 3; public static final int PRICE_FORMAT_SUFFIX_WITH_BLANK = 4; public static final String[] PRICE_FORMATS = { "", "¥", "元", "¥ ", " 元" }; /**获取价格,保留两位小数 * @param price * @return */ public static String getPrice(String price) { return getPrice(price, PRICE_FORMAT_DEFAULT); } //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/182 /**获取价格,保留两位小数 * @param price -price passed in * @param formatType 添加单位(元) * @return limit String *

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

*/ public static String getPrice(String price, int formatType) { if (isNotEmpty(price, true) == false) { return getPrice(0, formatType); } //单独写到getCorrectPrice? <<<<<<<<<<<<<<<<<<<<<< String correctPrice; StringBuilder correctPriceBuilder = new StringBuilder(""); String s; for (int i = 0; i < price.length(); i++) { s = price.substring(i, i + 1); if (".".equals(s) || isNumber(s)) { correctPriceBuilder.append(s); } } correctPrice = correctPriceBuilder.toString(); //单独写到getCorrectPrice? >>>>>>>>>>>>>>>>>>>>>> Log.i(TAG, "getPrice <<<<<<<<<<<<<<<<<< correctPrice = " + correctPrice); if (correctPrice.contains(".")) { // if (correctPrice.startsWith(".")) { // correctPrice = 0 + correctPrice; // } if (correctPrice.endsWith(".")) { correctPrice = correctPrice.replaceAll(".", ""); } } Log.i(TAG, "getPrice correctPrice = " + correctPrice + " >>>>>>>>>>>>>>>>"); return isNotEmpty(correctPrice, true) ? getPrice(new BigDecimal(0 + correctPrice), formatType) : getPrice(0, formatType); } /**获取价格,保留两位小数 * @param price * @return */ public static String getPrice(BigDecimal price) { return getPrice(price, PRICE_FORMAT_DEFAULT); } /**获取价格,保留两位小数 * @param price * @return */ public static String getPrice(double price) { return getPrice(price, PRICE_FORMAT_DEFAULT); } /**获取价格,保留两位小数 * @param price * @param formatType 添加单位(元) * @return */ public static String getPrice(BigDecimal price, int formatType) { return getPrice(price == null ? 0 : price.doubleValue(), formatType); } /**获取价格,保留两位小数 * @param price * @param formatType 添加单位(元) * @return */ public static String getPrice(double price, int formatType) { String s = new DecimalFormat("#########0.00").format(price); switch (formatType) { case PRICE_FORMAT_PREFIX: return PRICE_FORMATS[PRICE_FORMAT_PREFIX] + s; case PRICE_FORMAT_SUFFIX: return s + PRICE_FORMATS[PRICE_FORMAT_SUFFIX]; case PRICE_FORMAT_PREFIX_WITH_BLANK: return PRICE_FORMATS[PRICE_FORMAT_PREFIX_WITH_BLANK] + s; case PRICE_FORMAT_SUFFIX_WITH_BLANK: return s + PRICE_FORMATS[PRICE_FORMAT_SUFFIX_WITH_BLANK]; default: return s; } } public static String join(String[] arr) { return join(arr); } /** 数组以指定分隔s拼接 * @param arr * @param s * @return */ public static String join(String[] arr, String s) { if (s == null) { s = ","; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < arr.length; i++) { sb.append(arr[i]); if (i < arr.length-1) { sb.append(s); } } return sb.toString(); } /**分割路径 * @param path * @return */ public static String[] splitPath(String path) { if (StringUtil.isNotEmpty(path, true) == false) { return null; } return isPath(path) ? split(path, SEPARATOR) : new String[] {path}; } /**将s分割成String[] * @param s * @return */ public static String[] split(String s) { return split(s, null); } /**将s用split分割成String[] * trim = true; * @param s * @param split * @return */ public static String[] split(String s, String split) { return split(s, split, true); } /**将s用split分割成String[] * @param s * @param trim 去掉前后两端的split * @return */ public static String[] split(String s, boolean trim) { return split(s, null, trim); } /**将s用split分割成String[] * @param s * @param split * @param trim 去掉前后两端的split * @return */ public static String[] split(String s, String split, boolean trim) { s = get(s); if (s.isEmpty()) { return null; } if (isEmpty(split, false)) { split = ","; } if (trim) { while (s.startsWith(split)) { s = s.substring(split.length()); } while (s.endsWith(split)) { s = s.substring(0, s.length() - split.length()); } } return s.contains(split) ? s.split(split) : new String[]{s}; } /** * @param key * @param suffix * @return key + suffix,第一个字母小写 */ public static String addSuffix(String key, String suffix) { key = noBlank(key); if (key.isEmpty()) { return firstCase(suffix); } return firstCase(key) + firstCase(suffix, true); } /** * @param key */ public static String firstCase(String key) { return firstCase(key, false); } /** * @param key * @param upper * @return */ public static String firstCase(String key, boolean upper) { key = get(key); if (key.isEmpty()) { return ""; } String first = key.substring(0, 1); key = (upper ? first.toUpperCase() : first.toLowerCase()) + key.substring(1, key.length()); return key; } /**全部大写 * @param s * @return */ public static String toUpperCase(String s) { return toUpperCase(s, false); } /**全部大写 * @param s * @param trim * @return */ public static String toUpperCase(String s, boolean trim) { s = trim ? trim(s) : get(s); return s.toUpperCase(); } /**全部小写 * @param s * @return */ public static String toLowerCase(String s) { return toLowerCase(s, false); } /**全部小写 * @param s * @return */ public static String toLowerCase(String s, boolean trim) { s = trim ? trim(s) : get(s); return s.toLowerCase(); } public static String concat(String left, String right) { return concat(left, right, null); } public static String concat(String left, String right, String split) { return concat(left, right, split, true); } public static String concat(String left, String right, boolean trim) { return concat(left, right, null, trim); } public static String concat(String left, String right, String split, boolean trim) { if (isEmpty(left, trim)) { return right; } if (isEmpty(right, trim)) { return left; } if (split == null) { split = ","; } return left + split + right; } //校正(自动补全等)字符串>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> public static boolean equals(Object s1, Object s2) { return Objects.equals(s1, s2); } public static boolean equalsIgnoreCase(String s1, String s2) { if (s1 == s2) { return true; } if (s1 == null || s2 == null) { return false; } return s1.equalsIgnoreCase(s2); } } ================================================ FILE: APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java ================================================ /*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.exception.UnsupportedDataTypeException; import apijson.orm.script.ScriptExecutor; import java.lang.invoke.WrongMethodTypeException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.*; import static apijson.orm.AbstractSQLConfig.PATTERN_SCHEMA; /**可远程调用的函数类 * @author Lemon */ 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, ? 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 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 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 == null ? RequestMethod.GET : method; } @Override public AbstractFunctionParser setMethod(RequestMethod method) { this.method = method; return this; } @Override public String getTag() { return tag; } @Override public AbstractFunctionParser setTag(String tag) { this.tag = tag; return this; } @Override public int getVersion() { return version; } @Override public AbstractFunctionParser setVersion(int version) { this.version = version; return this; } @Override public String getKey() { return key; } @Override public AbstractFunctionParser setKey(String key) { this.key = key; return this; } @Override public String getParentPath() { return parentPath; } @Override public AbstractFunctionParser setParentPath(String parentPath) { this.parentPath = parentPath; return this; } @Override public String getCurrentName() { return currentName; } @Override public AbstractFunctionParser setCurrentName(String currentName) { this.currentName = currentName; return this; } @NotNull @Override public M getRequest() { return request; } @Override public AbstractFunctionParser setRequest(@NotNull M request) { this.request = request; return this; } @NotNull @Override public M getCurrentObject() { return current; } @Override 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 current 不作为第一个参数,就不能远程调用invoke,避免死循环 * @return {@link #invoke(String, M, boolean)} */ @Override public Object invoke(@NotNull String function, @NotNull M current) throws Exception { return invoke(function, current, false); } /**反射调用 * @param function 例如get(object,key),参数只允许引用,不能直接传值 * @param current 不作为第一个参数,就不能远程调用invoke,避免死循环 * @param containRaw 包含原始 SQL 片段 * @return {@link #invoke(AbstractFunctionParser, String, M, boolean)} */ @Override public Object invoke(@NotNull String function, @NotNull M current, boolean containRaw) throws Exception { if (StringUtil.isEmpty(function, true)) { throw new IllegalArgumentException("字符 " + function + " 不合法!"); } return invoke(this, function, current, containRaw); } /**反射调用 * @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() + " !"); } 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 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((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(), (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()) + " 不在后端 " + parser.getClass().getName() + " 内,也不在父类中!如果需要则先新增对应方法!" + "\n请检查函数名和参数数量是否与已定义的函数一致!" + "\n且必须为 function(key0,key1,...) 这种单函数格式!" + "\nfunction 必须符合 Java 函数命名,key 是用于在 curObj 内取值的键!" + "\n调用时不要有空格!" + (Log.DEBUG ? e.getMessage() : "")); } if (e instanceof InvocationTargetException) { Throwable te = ((InvocationTargetException) e).getTargetException(); if (StringUtil.isEmpty(te.getMessage(), true) == false) { //到处把函数声明throws Exception改成throws Throwable挺麻烦 throw te instanceof Exception ? (Exception) te : new Exception(te.getMessage()); } throw new IllegalArgumentException("字符 " + function + " 对应的远程函数传参类型错误!" + "\n请检查 key:value 中value的类型是否满足已定义的函数 " + getFunction(fb.getMethod(), fb.getKeys()) + " 的要求!" + (Log.DEBUG ? e.getMessage() : "")); } throw e; } } /**反射调用 * @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); } /**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 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); 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 || 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] = getArgValue(request, keys[i], containRaw); // request.get(keys[i]); if (v == null) { types[i] = Object.class; values[i] = null; break; } if (v instanceof Boolean) { types[i] = Boolean.class; //只支持JSON的几种类型 } // 怎么都有 bug,如果是引用的值,很多情况下无法指定 // 用 1L 指定为 Long ? 其它的默认按长度分配为 Integer 或 Long? //else if (v instanceof Long || v instanceof Integer || v instanceof Short) { // types[i] = Long.class; //} else if (v instanceof Number) { types[i] = Number.class; } else if (v instanceof String) { types[i] = String.class; } else if (v instanceof Map) { // 泛型兼容? // JSONMap types[i] = Map.class; //性能比较差 //values[i] = TypeUtils.cast(v, Map.class, ParserConfig.getGlobalInstance()); } 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 { 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] = Object.class; // 泛型擦除 JSON.JSON_OBJECT_CLASS; types[0] = cls; values = new Object[length + 1]; values[0] = request; for (int i = 0; i < length; i++) { types[i + 1] = String.class; values[i + 1] = keys[i]; } } FunctionBean fb = new FunctionBean(); fb.setFunction(function); fb.setSchema(schema); fb.setMethod(method); fb.setKeys(keys); fb.setTypes(types); fb.setValues(values); 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 * @param keys * @return */ public static String getFunction(String method, String[] keys) { String f = method + "(JSONMap request"; if (keys != null) { for (int i = 0; i < keys.length; i++) { f += (", String " + keys[i]); } } f += ")"; 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; private Object[] values; public String getFunction() { return function; } public void setFunction(String function) { this.function = function; } public String getSchema() { return schema; } public void setSchema(String schema) { this.schema = schema; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String[] getKeys() { return keys; } public void setKeys(String[] keys) { this.keys = keys; } public Class[] getTypes() { return types; } public void setTypes(Class[] types) { this.types = types; } public Object[] getValues() { return values; } public void setValues(Object[] values) { this.values = values; } /** * @param useValue * @return */ public String toFunctionCallString(boolean useValue) { return toFunctionCallString(useValue, null); } /** * @param useValue * @param quote * @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(); if (args != null && args.length > 0) { if (quote == null) { quote = "'"; } Object arg; for (int i = 0; i < args.length; i++) { arg = args[i]; s += (i <= 0 ? "" : ",") + (arg instanceof Boolean || arg instanceof Number ? arg : quote + arg + quote); } } return s + ")"; } } /** * 获取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; } } } ================================================ FILE: APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java ================================================ /*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.AbstractFunctionParser.FunctionBean; import apijson.orm.exception.ConflictException; import apijson.orm.exception.CommonException; import apijson.orm.exception.NotExistException; import apijson.orm.exception.UnsupportedDataTypeException; import java.rmi.ServerException; import java.util.*; import java.util.Map.Entry; 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, L extends List> implements ObjectParser { private static final String TAG = "AbstractObjectParser"; @NotNull protected AbstractParser parser; @Override public AbstractParser getParser() { return parser; } @Override public AbstractObjectParser setParser(Parser parser) { this.parser = (AbstractParser) parser; return this; } protected M request;//不用final是为了recycle protected String parentPath;//不用final是为了recycle protected SQLConfig arrayConfig;//不用final是为了recycle protected boolean isSubquery; protected final int type; protected final String arrayTable; protected final List> joinList; protected final boolean isTable; protected final boolean isArrayMainTable; protected final boolean tri; /** * TODO Parser内要不因为 非 TYPE_ITEM_CHILD_0 的Table 为空导致后续中断。 */ protected final boolean drop; private List stringKeyList; private List trimKeyList; /**for single object */ 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!!!"); } this.request = request; this.parentPath = parentPath; this.arrayConfig = arrayConfig; this.isSubquery = isSubquery; this.type = arrayConfig == null ? 0 : arrayConfig.getType(); this.arrayTable = arrayConfig == null ? null : arrayConfig.getTable(); this.joinList = arrayConfig == null ? null : arrayConfig.getJoinList(); this.isTable = isTable; // apijson.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; this.objectCount = 0; this.arrayCount = 0; boolean isEmpty = request.isEmpty();//empty有效 User:{} if (isEmpty) { this.tri = false; this.drop = false; } else { 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); String str = getString(request, KEY_STRING); String[] sks = StringUtil.split(str); stringKeyList = sks == null || sks.length <= 0 ? null : Arrays.asList(sks); request.remove(KEY_STRING); String trim = getString(request, KEY_TRIM); String[] trims = StringUtil.split(trim); trimKeyList = trims == null || trims.length <= 0 ? null : Arrays.asList(trims); request.remove(KEY_TRIM); } } @Override public String getParentPath() { return parentPath; } @Override 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) { this.position = position; return this; } private boolean invalidate = false; public void invalidate() { invalidate = true; } public boolean isInvalidate() { return invalidate; } private boolean breakParse = false; public void breakParse() { breakParse = true; } public boolean isBreakParse() { return breakParse || isInvalidate(); } protected String name; protected String table; protected String alias; protected boolean isReuse; protected String path; protected M response; protected M sqlRequest; protected M sqlResponse; /** * 自定义关键词 */ protected Map customMap; /** * 远程函数 * {"-":{ "key-()":value }, "0":{ "key()":value }, "+":{ "key+()":value } } * - : 在executeSQL前解析 * 0 : 在executeSQL后、onChildParse前解析 * + : 在onChildParse后解析 */ protected Map> functionMap; /** * 子对象 */ 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 { if (isInvalidate() == false) { this.isReuse = isReuse; this.name = name; this.path = AbstractParser.getAbsPath(parentPath, name); apijson.orm.Entry tentry = Pair.parseEntry(name, true); this.table = tentry.getKey(); this.alias = tentry.getValue(); Log.d(TAG, "AbstractObjectParser parentPath = " + parentPath + "; name = " + name + "; table = " + table + "; alias = " + alias); Log.d(TAG, "AbstractObjectParser type = " + type + "; isTable = " + isTable + "; isArrayMainTable = " + isArrayMainTable); Log.d(TAG, "AbstractObjectParser isEmpty = " + request.isEmpty() + "; tri = " + tri + "; drop = " + drop); breakParse = false; response = JSON.createJSONObject(); // must init sqlResponse = null; // must init if (isReuse == false) { sqlRequest = JSON.createJSONObject(); // must init customMap = null; // must init functionMap = null; // must init childMap = null; // must init Set> set = request.isEmpty() ? null : new LinkedHashSet<>(request.entrySet()); if (set != null && set.isEmpty() == false) { // 判断换取少几个变量的初始化是否值得? if (isTable) { // 非Table下必须保证原有顺序!否则 count,page 会丢, total@:"/[]/total" 会在[]:{}前执行! customMap = new LinkedHashMap(); childMap = new LinkedHashMap(); } functionMap = new LinkedHashMap>();//必须执行 // 条件 <<<<<<<<<<<<<<<<<<< List whereList = null; if (method == PUT) { // 这里只有PUTArray需要处理 || method == DELETE) { String[] combine = StringUtil.split(getString(request, KEY_COMBINE)); if (combine != null) { String w; for (int i = 0; i < combine.length; i++) { // 去除 &,|,! 前缀 w = combine[i]; if (w != null && (w.startsWith("&") || w.startsWith("|") || w.startsWith("!"))) { combine[i] = w.substring(1); } } } // Arrays.asList() 返回值不支持 add 方法! whereList = new ArrayList(Arrays.asList(combine != null ? combine : new String[]{})); whereList.add(JSONMap.KEY_ID); whereList.add(JSONMap.KEY_ID_IN); // whereList.add(apijson.JSONMap.KEY_USER_ID); // whereList.add(apijson.JSONMap.KEY_USER_ID_IN); } // 条件>>>>>>>>>>>>>>>>>>> int index = 0; // hasOtherKeyNotFun = false; M viceItem = null; for (Entry entry : set) { if (isBreakParse()) { break; } // 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; } // 处理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 { boolean startsWithAt = key.startsWith("@"); // if (startsWithAt || (key.endsWith("()") == false)) { // hasOtherKeyNotFun = true; // } boolean isTrim = (trimKeyList != null && trimKeyList.contains(key)); boolean toStr = isTrim || (stringKeyList != null && stringKeyList.contains(key)); if (toStr) { value = JSON.toJSONString(value); } if (isTrim && value != null) { value = StringUtil.trim(value); } if (startsWithAt || key.endsWith("@") || (key.endsWith("<>") && value instanceof Map)) { if (onParse(key, value) == false) { invalidate(); } } else if (value instanceof Map) { // JSONRequest,往下一级提取 if (childMap != null) { // 添加到childMap,最后再解析 childMap.put(key, (M) value); } else { // 直接解析并替换原来的,[]:{} 内必须直接解析,否则会因为丢掉count等属性,并且total@:"/[]/total"必须在[]:{} 后! 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 List && JSONMap.isTableArray(key)) { // L,批量新增或修改,往下一级提取 onTableArrayParse(key, (L) 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 { // L 或其它 Object,直接填充 if (onParse(key, value) == false) { invalidate(); } } } catch (Exception e) { if (tri == false) { throw CommonException.wrap(e, sqlConfig); // 不忽略错误,抛异常 } invalidate(); // 忽略错误,还原request } } } if (isTable) { // 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); } String ns = parser.getGlobalNamespace(); if (ns != null) { sqlRequest.putIfAbsent(JSONMap.KEY_NAMESPACE, ns); } String cl = parser.getGlobalCatalog(); if (cl != null) { sqlRequest.putIfAbsent(JSONMap.KEY_CATALOG, cl); } 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); } String cache = parser.getGlobalCache(); if (cache != null) { sqlRequest.putIfAbsent(JSONMap.KEY_CACHE, cache); } } } } if (isTable) { // 非Table内的函数会被滞后在onChildParse后调用 onFunctionResponse("-"); } } if (isInvalidate()) { recycle(); return null; } return this; } //private boolean hasOtherKeyNotFun = false; /**解析普通成员 * @param key * @param value * @return whether parse succeed */ @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 Map) { // key{}@ getRealKey, SQL 子查询对象,JSONRequest -> SQLConfig.getSQL String replaceKey = key.substring(0, key.length() - 1); 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); M obj = arr == null || arr.isEmpty() ? null : JSON.get(arr, 0); if (obj == null) { throw new Exception("服务器内部错误,解析子查询 " + path + "/" + key + ":{ } 为 Subquery 对象失败!"); } 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 + ":{} 不存在!"); } SQLConfig cfg = (SQLConfig) arrObj.get(AbstractParser.KEY_CONFIG); if (cfg == null) { throw new NotExistException(TAG + ".onParse cfg == null"); } Subquery s = new Subquery(); s.setPath(path); s.setOriginKey(key); s.setOriginValue(subquery); s.setFrom(from); s.setRange(range); s.setKey(replaceKey); s.setConfig(cfg); key = replaceKey; value = s; //(range == null || range.isEmpty() ? "" : "range") + "(" + cfg.getSQL(false) + ") "; parser.putQueryResult(AbstractParser.getAbsPath(path, key), s); //字符串引用保证不了安全性 parser.getSQL(cfg)); } else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 String replaceKey = key.substring(0, key.length() - 1); // 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)会出错 Log.d(TAG, "onParse target == null >> return true;"); if (Log.DEBUG) { parser.putWarnIfNeed(AbstractParser.KEY_REF, path + "/" + key + ": " + targetPath + " 引用赋值获取路径对应的值为 null!请检查路径是否错误!"); } // 非查询关键词 @key 不影响查询,直接跳过 if (isTable && (key.startsWith("@") == false || JSONMap.TABLE_KEY_LIST.contains(key))) { Log.e(TAG, "onParse isTable && (key.startsWith(@) == false" + " || apijson.JSONMap.TABLE_KEY_LIST.contains(key)) >> return null;"); // FIXME getCache() != null 时 return true,解决 RIGHT/OUTER/FOREIGN JOIN 主表无数据导致副表数据也不返回 return false; // 获取不到就不用再做无效的 query 了。不考虑 Table:{Table:{}} 嵌套 } 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; Log.d(TAG, "onParse key = " + key + "; value = " + value); } else { throw new IllegalArgumentException(path + "/" + key + ":value 中 value 必须为 依赖路径String 或 SQL子查询JSONObject !"); } } if (key.endsWith("()")) { if (value instanceof String == false) { throw new IllegalArgumentException(path + "/" + key + ":value 中 value 必须为函数String!"); } String k = key.substring(0, key.length() - 2); 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); } else if (isPlus) { type = "+"; k = k.substring(0, k.length() - 1); } else { type = "0"; } 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) { map = new LinkedHashMap<>(); } map.put(k, (String) value); functionMap.put(type, map); } } else if (isTable && key.startsWith("@") && JSONMap.TABLE_KEY_LIST.contains(key) == false) { customMap.put(key, value); } else { sqlRequest.put(key, value); } return true; } /** * @param index * @param key * @param value * @param cache * @return * @throws Exception */ @Override public Object onChildParse(int index, String key, M value, Object cache) throws Exception { boolean isFirst = index <= 0; boolean isMain = isFirst && type == TYPE_ITEM; Object child; boolean isEmpty; if (JSONMap.isArrayKey(key)) { // APIJSON Array if (isMain) { throw new IllegalArgumentException(parentPath + "/" + key + ":{} 不合法!" + "数组 []:{} 中第一个 key:{} 必须是主表 TableKey:{} !不能为 arrayKey[]:{} !"); } if (arrayConfig == null || arrayConfig.getPosition() == 0) { arrayCount ++; int maxArrayCount = parser.getMaxArrayCount(); if (arrayCount > maxArrayCount) { throw new IllegalArgumentException(path + " 内截至 " + key + ":{} 时数组对象 key[]:{} " + "的数量达到 " + arrayCount + " 已超限,必须在 0-" + maxArrayCount + " 内 !"); } } 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 = 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 && (arrayConfig == null || arrayConfig.getPosition() == 0)) { objectCount ++; int maxObjectCount = parser.getMaxObjectCount(); if (objectCount > maxObjectCount) { //TODO 这里判断是批量新增/修改,然后上限为 maxUpdateCount throw new IllegalArgumentException(path + " 内截至 " + key + ":{} 时对象" + " key:{} 的数量达到 " + objectCount + " 已超限,必须在 0-" + maxObjectCount + " 内 !"); } } 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 || ((Map) child).isEmpty(); if (isFirst && isEmpty) { invalidate(); } } // Log.i(TAG, "onChildParse ObjectParser.onParse key = " + key + "; child = " + child); return isEmpty ? null : child; // 只添加! isChildEmpty的值,可能数据库返回数据不够count } //TODO 改用 MySQL json_add,json_remove,json_contains 等函数!不过就没有具体报错了,或许可以新增功能符,或者直接调 SQL 函数 /**PUT key:[] * @param key * @param array * @throws Exception */ @Override 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;"); return; } int putType = 0; if (key.endsWith("+")) {//add putType = 1; } else if (key.endsWith("-")) {//remove putType = 2; } else {//replace sqlRequest.put(key, array); return; } String realKey = AbstractSQLConfig.gainRealKey(method, key, false, false); //GET > add all 或 remove all > PUT > remove key //GET <<<<<<<<<<<<<<<<<<<<<<<<< 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 <<<<<<<<<<<<<<<<<<<<<<<<< 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 (int i = 0; i < array.size(); i++) { Object obj = array.get(i); if (obj == null) { continue; } 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); } } 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); } } } //add all 或 remove all >>>>>>>>>>>>>>>>>>>>>>>>> //PUT <<<<<<<<<<<<<<<<<<<<<<<<< sqlRequest.put(realKey, targetArray != null ? targetArray : JSON.toJSONString(targetObj)); // FIXME, SerializerFeature.WriteMapNullValue)); //PUT >>>>>>>>>>>>>>>>>>>>>>>>> } @Override public void onTableArrayParse(String key, L valueArray) throws Exception { String childKey = key.substring(0, key.length() - JSONMap.KEY_ARRAY.length()); int allCount = 0; L ids = JSON.createJSONArray(); int version = parser.getVersion(); int maxUpdateCount = parser.getMaxUpdateCount(); 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 性能更高,报错也更会更好处理,更人性化 M item; try { item = JSON.get(valueArray, i); if (item == null) { throw new NullPointerException(); } } catch (Exception e) { throw new UnsupportedDataTypeException( "批量新增/修改失败!" + key + "/" + i + ":value 中value不合法!类型必须是 OBJECT ,结构为 {} !" ); } 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); } 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 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 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 { 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 * @return {@link #setSQLConfig(int, int, int)} * @throws Exception */ @Override 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 { if (isTable == false || isReuse) { return setPosition(position); } if (sqlConfig == null) { try { sqlConfig = newSQLConfig(false); } 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); parser.onVerifyRole(sqlConfig); return this; } protected SQLConfig sqlConfig = null;//array item复用 /**SQL查询,for array item * @return this * @throws Exception */ @Override public AbstractObjectParser executeSQL() throws Exception { //执行SQL操作数据库 if (isTable == false) {//提高性能 sqlResponse = JSON.createJSONObject(); sqlResponse.putAll(sqlRequest); } 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; } /** * @return response * @throws Exception */ @Override public M response() throws Exception { if (sqlResponse == null || sqlResponse.isEmpty()) { if (isTable) {//Table自身都获取不到值,则里面的Child都无意义,不需要再解析 return null; // response; } } else { response.putAll(sqlResponse); } //把isTable时取出去的custom重新添加回来 if (customMap != null) { response.putAll(customMap); } onFunctionResponse("0"); onChildResponse(); onFunctionResponse("+"); onComplete(); return response; } @Override public void onFunctionResponse(String type) throws Exception { Map map = functionMap == null ? null : functionMap.get(type); //解析函数function Set> functionSet = map == null ? null : map.entrySet(); if (functionSet != null && functionSet.isEmpty() == false) { boolean isMinus = "-".equals(type); M json = isMinus ? sqlRequest : response; // key-():function 是实时执行,而不是在这里批量执行 for (Entry entry : functionSet) { 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, 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; boolean containRaw = rawKeyList != null && rawKeyList.contains(rawKey); boolean isProcedure = key.startsWith("@"); if (isProcedure) { FunctionBean fb = AbstractFunctionParser.parseFunction(value, currentObject, true, containRaw); SQLConfig config = newSQLConfig(true); String sch = fb.getSchema(); if (StringUtil.isNotEmpty(sch, true)) { config.setSchema(sch); } config.setProcedure(fb.toFunctionCallString(true)); result = parseResponse(config, true); key = key.substring(1); } else { result = parser.onFunctionParse(key, value, parentPath, currentName, currentObject, containRaw); } String k = AbstractSQLConfig.gainRealKey(method, key, false, false); 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(); if (set != null) { int index = 0; for (Entry entry : set) { Object child = entry == null ? null : onChildParse(index, entry.getKey(), entry.getValue(), null); if (child == null || (child instanceof Map && ((M) child).isEmpty()) || (child instanceof List && ((L) child).isEmpty()) ) { continue; } response.put(entry.getKey(), child); index ++; } } } @Override public Object onReferenceParse(@NotNull String path) { return parser.getValueByPath(path); } @SuppressWarnings("unchecked") @Override public M onSQLExecute() throws Exception { int position = getPosition(); 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 = result == null ? null : (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (isArrayMainTable && position == 0 && rawList != null) { isSimpleArray = (functionMap == null || functionMap.isEmpty()) && (customMap == null || customMap.isEmpty()) && (childMap == null || childMap.isEmpty()) && (table.equals(arrayTable)); // APP JOIN 副表时副表返回了这个字段 rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); if (isSimpleArray) { parser.putQueryResult(arrayPath, rawList); // 从数组外部引用该数组内值需要 } else { long startTime = System.currentTimeMillis(); for (int i = 1; i < rawList.size(); i++) { // 从 1 开始,0 已经处理过 M obj = rawList.get(i); 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 "); } parser.putArrayMainCache(arrayPath, rawList); } if (isSubquery == false && result != null) { parser.putQueryResult(path, result); // 解决获取关联数据时requestObject里不存在需要的关联数据 if (isSimpleArray) { // FIXME 改为从缓存获取,而不是 result 查 result.put(AbstractSQLExecutor.KEY_RAW_LIST, rawList); } } } return result; } /** * response has the final value after parse (and query if isTable) */ @Override public void onComplete() { } /**回收内存 */ @Override public void recycle() { //后面还可能用到,要还原 if (tri) {//避免返回未传的字段 request.put(KEY_TRY, tri); } if (drop) { request.put(KEY_DROP, drop); } if (stringKeyList != null) { // 避免被全局关键词覆盖 && ! stringKeyList.isEmpty()) { request.put(KEY_STRING, StringUtil.get(stringKeyList.toArray())); } if (trimKeyList != null) { // 避免被全局关键词覆盖 && ! trimKeyList.isEmpty()) { request.put(KEY_TRIM, StringUtil.get(trimKeyList.toArray())); } method = null; parentPath = null; arrayConfig = null; // if (response != null) { // response.clear();//有效果? // response = null; // } request = null; response = null; sqlRequest = null; sqlResponse = null; functionMap = null; customMap = null; childMap = null; } protected RequestMethod method; @Override public AbstractObjectParser setMethod(RequestMethod method) { if (this.method != method) { this.method = method; sqlConfig = null; //TODO ? sqlResponse = null; } return this; } @Override public RequestMethod getMethod() { return method; } @Override public boolean isTable() { return isTable; } @Override public String getPath() { return path; } @Override public String getTable() { return table; } @Override public String getAlias() { return alias; } @Override public SQLConfig getArrayConfig() { return arrayConfig; } @Override public SQLConfig getSQLConfig() { return sqlConfig; } @Override public M getResponse() { return response; } @Override public M getSQLRequest() { return sqlRequest; } @Override public M getSQLResponse() { return sqlResponse; } @Override public Map getCustomMap() { return customMap; } @Override public Map> getFunctionMap() { return functionMap; } @Override public Map getChildMap() { return childMap; } } ================================================ FILE: APIJSONORM/src/main/java/apijson/orm/AbstractParser.java ================================================ /*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.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.*; import java.util.Map.Entry; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.Query; 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, L extends List> implements Parser { protected static final String TAG = "AbstractParser"; /** * JSON 对象、数组对应的数据源、版本、角色、method等 */ protected Map> keyObjectAttributesMap = new HashMap<>(); /** * 可以通过切换该变量来控制是否打印关键的接口请求内容。保守起见,该值默认为false。 * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求内容。 */ public static boolean IS_PRINT_REQUEST_STRING_LOG = false; /** * 打印大数据量日志的标识。线上环境比较敏感,可以通过切换该变量来控制异常栈抛出、错误日志打印。保守起见,该值默认为false。 * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求及响应信息。 */ public static boolean IS_PRINT_BIG_LOG = false; /** * 可以通过切换该变量来控制是否打印关键的接口请求结束时间。保守起见,该值默认为false。 * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求结束时间。 */ 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; /** * 分页页码是否从 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 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; } @Override public int getMaxUpdateCount() { return MAX_UPDATE_COUNT; } @Override public int getMaxSQLCount() { return MAX_SQL_COUNT; } @Override public int getMaxObjectCount() { return MAX_OBJECT_COUNT; } @Override public int getMaxArrayCount() { return MAX_ARRAY_COUNT; } @Override public int getMaxQueryDepth() { return MAX_QUERY_DEPTH; } /** * method = null */ public AbstractParser() { this(null); } /**needVerify = true * @param method null ? requestMethod = GET */ public AbstractParser(RequestMethod method) { super(); setMethod(method); setNeedVerifyRole(AbstractVerifier.ENABLE_VERIFY_ROLE); setNeedVerifyContent(AbstractVerifier.ENABLE_VERIFY_CONTENT); } /** * @param method null ? requestMethod = GET * @param needVerify 仅限于为服务端提供方法免验证特权,普通请求不要设置为 false ! 如果对应Table有权限也建议用默认值 true,保持和客户端权限一致 */ public AbstractParser(RequestMethod method, boolean needVerify) { super(); setMethod(method); setNeedVerify(needVerify); } protected boolean isRoot = true; public boolean isRoot() { return 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; @NotNull @Override public Visitor getVisitor() { if (visitor == null) { visitor = new Visitor() { @Override public T getId() { return null; } @Override public List getContactIdList() { return null; } }; } return visitor; } @Override public AbstractParser setVisitor(@NotNull Visitor visitor) { this.visitor = visitor; return this; } protected RequestMethod requestMethod; @NotNull @Override public RequestMethod getMethod() { return requestMethod; } @NotNull @Override 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; } protected int version; @Override public int getVersion() { return version; } @Override public AbstractParser setVersion(int version) { this.version = version; return this; } protected String tag; @Override public String getTag() { return tag; } @Override public AbstractParser setTag(String tag) { this.tag = tag; return this; } protected String requestURL; public String getRequestURL() { return requestURL; } public AbstractParser setRequestURL(String requestURL) { this.requestURL = requestURL; return this; } protected M requestObject; @Override public M getRequest() { return requestObject; } @Override public AbstractParser setRequest(M request) { this.requestObject = request; return this; } protected Boolean globalFormat; public AbstractParser setGlobalFormat(Boolean globalFormat) { this.globalFormat = globalFormat; return this; } @Override public Boolean getGlobalFormat() { return globalFormat; } protected String globalRole; public AbstractParser setGlobalRole(String globalRole) { this.globalRole = globalRole; return this; } @Override public String getGlobalRole() { return globalRole; } protected String globalDatabase; public AbstractParser setGlobalDatabase(String globalDatabase) { this.globalDatabase = globalDatabase; return this; } @Override public String getGlobalDatabase() { return globalDatabase; } protected String globalDatasource; @Override public String getGlobalDatasource() { return 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) { this.globalExplain = globalExplain; return this; } @Override public Boolean getGlobalExplain() { return globalExplain; } protected String globalCache; public AbstractParser setGlobalCache(String globalCache) { this.globalCache = globalCache; return this; } @Override public String getGlobalCache() { return globalCache; } @Override public AbstractParser setNeedVerify(boolean needVerify) { setNeedVerifyLogin(needVerify); setNeedVerifyRole(needVerify); setNeedVerifyContent(needVerify); return this; } protected boolean needVerifyLogin; @Override public boolean isNeedVerifyLogin() { return needVerifyLogin; } @Override public AbstractParser setNeedVerifyLogin(boolean needVerifyLogin) { this.needVerifyLogin = needVerifyLogin; return this; } protected boolean needVerifyRole; @Override public boolean isNeedVerifyRole() { return needVerifyRole; } @Override public AbstractParser setNeedVerifyRole(boolean needVerifyRole) { this.needVerifyRole = needVerifyRole; return this; } protected boolean needVerifyContent; @Override public boolean isNeedVerifyContent() { return needVerifyContent; } @Override public AbstractParser setNeedVerifyContent(boolean needVerifyContent) { this.needVerifyContent = needVerifyContent; return this; } protected SQLExecutor sqlExecutor; protected Verifier verifier; protected Map queryResultMap;//path-result @Override public SQLExecutor getSQLExecutor() { if (sqlExecutor == null) { sqlExecutor = createSQLExecutor(); } sqlExecutor.setParser(this); return sqlExecutor; } @Override 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 */ @Override public String parse(String request) { return JSON.toJSONString(parseResponse(request)); } /**解析请求json并获取对应结果 * @param request * @return */ @NotNull @Override public String parse(M request) { return JSON.toJSONString(parseResponse(request)); } /**解析请求json并获取对应结果 * @param request 先parseRequest中URLDecoder.decode(request, UTF_8);再parseResponse(getCorrectRequest(...)) * @return parseResponse(requestObject); */ @NotNull @Override public M parseResponse(String request) { Log.d(TAG, "\n\n\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n" + requestMethod + "/parseResponse request = \n" + request + "\n\n"); try { requestObject = JSON.parseObject(request); if (requestObject == null) { throw new UnsupportedEncodingException("JSON格式不合法!"); } } catch (Exception e) { return newErrorResult(e, isRoot); } return parseResponse(requestObject); } private int queryDepth; private long executedSQLDuration; /**解析请求json并获取对应结果 * @param request * @return requestObject */ @NotNull @Override 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()); if (RequestMethod.isPublicMethod(requestMethod) == false) { try { if (isNeedVerifyLogin()) { onVerifyLogin(); } if (isNeedVerifyContent()) { onVerifyContent(); } } catch (Exception e) { return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot); } } //必须在parseCorrectRequest后面,因为parseCorrectRequest可能会添加 @role if (isNeedVerifyRole() && globalRole == null) { try { setGlobalRole(getString(requestObject, KEY_ROLE)); requestObject.remove(KEY_ROLE); } catch (Exception e) { return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot); } } try { 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 = getSQLExecutor(); onBegin(); try { queryDepth = 0; executedSQLDuration = 0; requestObject = onObjectParse(request, null, null, null, false, null); onCommit(); } catch (Exception e) { Log.e(TAG, "onObjectParse failed", e); error = e; onRollback(); } 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); 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) { 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) { // 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 if (IS_PRINT_REQUEST_STRING_LOG || Log.DEBUG || error != null) { Log.sl("\n\n\n", '<', ""); Log.fd(TAG, requestMethod + "/parseResponse request = \n" + requestString + "\n\n"); } if (IS_PRINT_BIG_LOG || Log.DEBUG || error != null) { // 日志仅存服务器,所以不太敏感,而且这些日志虽然量大但非常重要,对排查 bug 很关键 Log.fd(TAG, requestMethod + "/parseResponse return response = \n" + JSON.toJSONString(requestObject) + "\n\n"); } if (IS_PRINT_REQUEST_ENDTIME_LOG || Log.DEBUG || error != null) { Log.fd(TAG, requestMethod + "/parseResponse endTime = " + endTime + "; duration = " + duration); Log.sl("", '>', "\n\n\n"); } return res; } @Override public void onVerifyLogin() throws Exception { getVerifier().verifyLogin(); } @Override public void onVerifyContent() throws Exception { requestObject = parseCorrectRequest(); } /**校验角色及对应操作的权限 * @param config * @return * @throws Exception */ @Override public void onVerifyRole(@NotNull SQLConfig config) throws Exception { if (Log.DEBUG) { Log.i(TAG, "onVerifyRole config = " + JSON.toJSONString(config)); } if (isNeedVerifyRole()) { if (config.getRole() == null) { if (globalRole != null) { config.setRole(globalRole); } else { config.setRole(getVisitor().getId() == null ? AbstractVerifier.UNKNOWN : AbstractVerifier.LOGIN); } } getVerifier().verifyAccess(config); } } @Override 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方式几乎肯定不安全 } return batchVerify(method, tag, version, name, request, maxUpdateCount, creator); } /**自动根据 tag 是否为 TableKey 及是否被包含在 object 内来决定是否包装一层,改为 { tag: object, "tag": tag } * @param object * @param tag * @return */ 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 = JSON.createJSONObject(); } object.put(KEY_TAG, tag); } return object; } boolean isDiffArrayKey = tag.endsWith(":[]"); boolean isArrayKey = isDiffArrayKey || isArrayKey(tag); String key = isArrayKey ? tag.substring(0, tag.length() - (isDiffArrayKey ? 3 : 2)) : tag; 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, JSON.createJSONArray()); } try { Map type = JSON.get(target, Operation.TYPE.name()); if (type == null || (type.containsKey(arrKey) == false)) { if (type == null) { type = new LinkedHashMap(); } type.put(arrKey, "OBJECT[]"); target.put(Operation.TYPE.name(), type); } } catch (Throwable e) { 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 = JSON.createJSONObject(); target.put(tag, object); } else if (target.containsKey(key) == false) { target = JSON.createJSONObject(); target.put(key, object); } } } if (putTag) { target.put(KEY_TAG, tag); } return target; } /**新建带状态内容的JSONObject * @param code * @param msg * @return */ public M newResult(int code, String msg) { return newResult(code, msg, null); } /** * 添加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 M newResult(int code, String msg, String warn, boolean isRoot) { return extendResult(null, code, msg, warn, isRoot); } /** * 添加JSONObject的状态内容,一般用于错误提示结果 * * @param object * @param code * @param msg * @return */ 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\n **环境信息** " + " \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 = JSON.createJSONObject(); } if (object.get(JSONResponse.KEY_OK) == null) { object.put(JSONResponse.KEY_OK, JSONResponse.isSuccess(code)); } if (object.get(JSONResponse.KEY_CODE) == null) { object.put(JSONResponse.KEY_CODE, code); } String m = StringUtil.get(getString(object, JSONResponse.KEY_MSG)); if (m.isEmpty() == false) { 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 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 M extendSuccessResult(M object, String warn, boolean isRoot) { return extendResult(object, JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, warn, isRoot); } /**获取请求成功的状态内容 * @return */ public M newSuccessResult() { return newSuccessResult(null); } /**获取请求成功的状态内容 * @param warn * @return */ public M newSuccessResult(String warn) { return newSuccessResult(warn, false); } /**获取请求成功的状态内容 * @param warn * @param isRoot * @return */ public M newSuccessResult(String warn, boolean isRoot) { return newResult(JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, warn, isRoot); } /**添加请求成功的状态内容 * @param object * @param e * @return */ public M extendErrorResult(M object, Throwable e) { return extendErrorResult(object, e, false); } /**添加请求成功的状态内容 * @param object * @param e * @param isRoot * @return */ public M extendErrorResult(M object, Throwable e, boolean isRoot) { return extendErrorResult(object, e, null, null, isRoot); } /**添加请求成功的状态内容 * @param object * @return */ 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 M newErrorResult(Exception e) { return newErrorResult(e, false); } /**新建错误状态内容 * @param e * @param isRoot * @return */ public M newErrorResult(Exception e, boolean isRoot) { if (e != null) { Log.e(TAG, "newErrorResult", e); String msg = CommonException.getMsg(e); int code = CommonException.getCode(e); return newResult(code, msg, null, isRoot); } return newResult(JSONResponse.CODE_SERVER_ERROR, JSONResponse.MSG_SERVER_ERROR, null, isRoot); } /**获取正确的请求,非GET请求必须是服务器指定的 * @return * @throws Exception */ @Override public M parseCorrectRequest() throws Exception { return parseCorrectRequest(requestMethod, tag, version, "", requestObject, getMaxUpdateCount(), this); } /**获取Request或Response内指定JSON结构 * @param table * @param method * @param tag * @param version * @return * @throws Exception */ @Override public M getStructure(@NotNull String table, String method, String tag, int version) throws Exception { String cacheKey = AbstractVerifier.getCacheKeyForRequest(method, tag); SortedMap> versionedMap = (SortedMap>) AbstractVerifier.REQUEST_MAP.get(cacheKey); 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(); if (set != null && set.isEmpty() == false) { Entry> maxEntry = null; for (Entry> entry : set) { if (entry == null || entry.getKey() == null || entry.getValue() == null) { continue; } if (version <= 0 || version == entry.getKey()) { // 这里应该不会出现相等,因为上面 versionedMap.get(Integer.valueOf(version)) maxEntry = entry; break; } if (entry.getKey() < version) { break; } maxEntry = entry; } result = maxEntry == null ? null : maxEntry.getValue(); } if (result != null) { // 加快下次查询,查到值的话组合情况其实是有限的,不属于恶意请求 if (versionedMap == null) { versionedMap = new TreeMap<>((o1, o2) -> { return o2 == null ? -1 : o2.compareTo(o1); // 降序 }); } versionedMap.put(Integer.valueOf(version), result); AbstractVerifier.REQUEST_MAP.put(cacheKey, versionedMap); } } if (result == null) { if (Log.DEBUG == false && AbstractVerifier.REQUEST_MAP.isEmpty() == false) { return null; // 已使用 REQUEST_MAP 缓存全部,但没查到 } // 获取指定的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(KEY_TAG, tag); if (version > 0) { where.put(KEY_VERSION + ">=", version); } config.setWhere(where); config.setOrder(KEY_VERSION + (version > 0 ? "+" : "-")); config.setCount(1); // too many connections error: 不try-catch,可以让客户端看到是服务器内部异常 result = getSQLExecutor().execute(config, false); // version, method, tag 组合情况太多了,JDK 里又没有 LRUCache,所以要么启动时一次性缓存全部后面只用缓存,要么每次都查数据库 // versionedMap.put(Integer.valueOf(version), result); // AbstractVerifier.REQUEST_MAP.put(cacheKey, versionedMap); } return JSON.get(result, "structure"); //解决返回值套了一层 "structure":{} } protected Map> arrayObjectParserCacheMap = new HashMap<>(); // protected SQLConfig itemConfig; /**获取单个对象,该对象处于parentObject内 * @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 */ @Override 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 + ";\n name = " + name + "; request = " + JSON.toJSONString(request)); } if (request == null) {// Moment:{} || request.isEmpty()) {//key-value条件 return null; } int type = arrayConfig == null ? 0 : arrayConfig.getType(); int position = arrayConfig == null ? 0 : arrayConfig.getPosition(); String[] arr = StringUtil.split(parentPath, "/"); if (position == 0) { int d = arr == null ? 1 : arr.length + 1; if (queryDepth < d) { queryDepth = d; int maxQueryDepth = getMaxQueryDepth(); if (queryDepth > maxQueryDepth) { throw new IllegalArgumentException(parentPath + "/" + name + ":{} 的深度(或者说层级) 为 " + queryDepth + " 已超限,必须在 1-" + maxQueryDepth + " 内 !"); } } } apijson.orm.Entry entry = Pair.parseEntry(name, true); String table = entry.getKey(); //Comment // String alias = entry.getValue(); //to 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; if (isReuse) { // 数组主表使用专门的缓存数据 op = arrayObjectParserCacheMap.get(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2)); op.setParentPath(parentPath); } if (op == null) { op = createObjectParser(request, parentPath, arrayConfig, isSubquery, isTable, isArrayMainTable); } // 对象 - 设置 method setOpMethod(request, op, name); op.setCache(cache); op = op.parse(name, isReuse); M response = null; if (op != null) {//SQL查询结果为空时,functionMap和customMap没有意义 if (arrayConfig == null) { //Common response = op.setSQLConfig().executeSQL().response(); } else {//Array Item Child int query = arrayConfig.getQuery(); //total 这里不能用arrayConfig.getType(),因为在createObjectParser.onChildParse传到onObjectParse时已被改掉 if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != apijson.JSONRequest.QUERY_TABLE && position == 0) { //TODO 应在这里判断 @column 中是否有聚合函数,而不是 AbstractSQLConfig.getColumnString Map rp; Boolean compat = arrayConfig.getCompat(); if (compat != null && compat) { // 解决对聚合函数字段通过 query:2 分页查总数返回值错误 // 这里可能改变了内部的一些数据,下方通过 arrayConfig 还原 SQLConfig cfg = op.setSQLConfig(0, 0, 0).getSQLConfig(); boolean isExplain = cfg.isExplain(); cfg.setExplain(false); Subquery subqy = new Subquery(); subqy.setFrom(cfg.getTable()); subqy.setConfig(cfg); SQLConfig countSQLCfg = createSQLConfig(); countSQLCfg.setColumn(Arrays.asList("count(*):count")); countSQLCfg.setFrom(subqy); rp = executeSQL(countSQLCfg, false); cfg.setExplain(isExplain); } else { // 对聚合函数字段通过 query:2 分页查总数返回值错误 RequestMethod method = op.getMethod(); rp = op.setMethod(RequestMethod.HEAD).setSQLConfig().executeSQL().getSQLResponse(); op.setMethod(method); } if (rp != null) { int index = parentPath.lastIndexOf("]/"); if (index >= 0) { int total = getIntValue(rp, JSONResponse.KEY_COUNT); String pathPrefix = parentPath.substring(0, index) + "]/"; putQueryResult(pathPrefix + JSONResponse.KEY_TOTAL, total); //详细的分页信息,主要为 PC 端提供 int count = arrayConfig.getCount(); int page = arrayConfig.getPage(); int max = (int) ((total - 1)/count); if (max < 0) { max = 0; } int min = getMinQueryPage(); page += min; max += min; M pagination = JSON.createJSONObject(); Object explain = rp.get(JSONResponse.KEY_EXPLAIN); if (explain instanceof Map) { pagination.put(JSONResponse.KEY_EXPLAIN, explain); } pagination.put(JSONResponse.KEY_TOTAL, total); 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 == min); pagination.put(JSONResponse.KEY_LAST, page == max); putQueryResult(pathPrefix + JSONResponse.KEY_INFO, pagination); if (total <= count*(page - min)) { query = apijson.JSONRequest.QUERY_TOTAL;//数量不够了,不再往后查询 } } } op.setMethod(requestMethod); } //Table if (query == apijson.JSONRequest.QUERY_TOTAL) { response = null;//不再往后查询 } else { response = op .setSQLConfig(arrayConfig.getCount(), arrayConfig.getPage(), position) .executeSQL() .response(); // itemConfig = op.getConfig(); } } if (isArrayMainTable) { if (position == 0) { // 提取并缓存数组主表的列表数据 arrayObjectParserCacheMap.put(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2), op); } } // else { // op.recycle(); // } op = null; } return response; } /**获取对象数组,该对象数组处于parentObject内 * @param request parentObject的value * @param parentPath parentObject的路径 * @param name parentObject的key * @param isSubquery 是否为子查询 * @param cache SQL 结果缓存 * @return * @throws Exception */ @Override 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"绕过权限并能批量查询 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 条件 return null; } String path = getAbsPath(parentPath, name); //不能改变,因为后面可能继续用到,导致1以上都改变 []:{0:{Comment[]:{0:{Comment:{}},1:{...},...}},1:{...},...} 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 = apijson.JSONRequest.QUERY_TABLE; } else { switch (query) { case "0": case apijson.JSONRequest.QUERY_TABLE_STRING: query2 = apijson.JSONRequest.QUERY_TABLE; break; case "1": case apijson.JSONRequest.QUERY_TOTAL_STRING: query2 = apijson.JSONRequest.QUERY_TOTAL; break; case "2": case apijson.JSONRequest.QUERY_ALL_STRING: query2 = apijson.JSONRequest.QUERY_ALL; break; default: throw new IllegalArgumentException(path + "/" + apijson.JSONRequest.KEY_QUERY + ":value 中 value 的值不合法!必须在 [0, 1, 2] 或 [TABLE, TOTAL, ALL] 内 !"); } } 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 + "/" + apijson.JSONRequest.KEY_PAGE + ":value 中 value 的值不合法!必须在 " + minPage + "-" + maxPage + " 内 !"); } //不用total限制数量了,只用中断机制,total只在query = 1,2的时候才获取 int count2 = isSubquery || count != null ? (count == null ? 0 : count) : getDefaultQueryCount(); int max = isSubquery ? count2 : getMaxQueryCount(); if (count2 < 0 || count2 > max) { throw new IllegalArgumentException(path + "/" + apijson.JSONRequest.KEY_COUNT + ":value 中 value 的值不合法!必须在 0-" + max + " 内 !"); } 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都无效!!! 后续都不执行,没必要还原数组关键词浪费性能 Log.e(TAG, "onArrayParse request.isEmpty() >> return null;"); return 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); //key[]:{Table:{}}中key equals Table时 提取Table int index = isSubquery || name == null ? -1 : name.lastIndexOf("[]"); String childPath = index <= 0 ? null : Pair.parseEntry(name.substring(0, index), true).getKey(); // Table-key1-key2... String arrTableKey = null; //判断第一个key,即Table是否存在,如果存在就提取 String[] childKeys = StringUtil.split(childPath, "-", false); if (childKeys == null || childKeys.length <= 0 || request.containsKey(childKeys[0]) == false) { childKeys = null; } else if (childKeys.length == 1 && isTableKey(childKeys[0])) { // 可能无需提取,直接返回 rawList 即可 arrTableKey = childKeys[0]; } //Table<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< List> joinList = onJoinParse(join, request); SQLConfig config = createSQLConfig() .setMethod(requestMethod) .setCount(size) .setPage(page2) .setQuery(query2) .setCompat(compat) .setTable(arrTableKey) .setJoinList(joinList); 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, null); if (parent == null || parent.isEmpty()) { break; } long startTime = System.currentTimeMillis(); /* 这里优化了 Table[]: { Table:{} } 这种情况下的性能 * 如果把 List> 改成 L 来减少以下 addAll 一次复制,则会导致 AbstractSQLExecutor 等其它很多地方 get 要改为 getJSONObject, * 修改类型会导致不兼容旧版依赖 ORM 的项目,而且整体上性能只有特殊情况下性能提升,其它非特殊情况下因为多出很多 instanceof Map 的判断而降低了性能。 */ 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); 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 = JSON.createJSONArray(list); long endTime = System.currentTimeMillis(); // 0ms Log.d(TAG, "\n onArrayParse <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n for (int i = 0; i < (isSubquery ? 1 : size); i++) " + " startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); break; } //key[]:{Table:{}}中key equals Table时 提取Table response.add(getValue(parent, childKeys)); //null有意义 } //Table>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /* * 支持引用取值后的数组 { "User-id[]": { "User": { "contactIdList<>": 82002 } }, "Moment-userId[]": { "Moment": { "userId{}@": "User-id[]" } } } */ if (isExtract) { long startTime = System.currentTimeMillis(); Object fo = childKeys == null || response.isEmpty() ? null : response.get(0); if (fo instanceof Boolean || fo instanceof Number || fo instanceof String) { //[{}] 和 [[]] 都没意义 putQueryResult(path, response); } long endTime = System.currentTimeMillis(); Log.d(TAG, "\n onArrayParse <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n isExtract >> putQueryResult " + " startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); } } finally { //后面还可能用到,要还原 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) { Log.i(TAG, "onArrayParse return response = \n" + JSON.toJSONString(response) + "\n>>>>>>>>>>>>>>>\n\n\n"); } return response; } private static final List JOIN_COPY_KEY_LIST; static { // TODO 不全 JOIN_COPY_KEY_LIST = new ArrayList(); 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 */ private List> onJoinParse(Object join, M request) throws Exception { Map joinMap = null; 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 LinkedHashMap(); //注意:这里必须要保证join连接顺序,保证后边遍历是按照join参数的顺序生成的SQL for (int i = 0; i < sArr.length; i++) { joinMap.put(sArr[i], new LinkedHashMap()); } } } else if (join != null){ 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); if (set == null || set.isEmpty()) { Log.e(TAG, "onJoinParse set == null || set.isEmpty() >> return null;"); return null; } List> joinList = new ArrayList<>(); for (Entry e : set) { // { &/User:{}, == false) { throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":value 中value不合法!" + "必须为 &/Table0/key0, ( ) <> () * // if (StringUtil.isEmpty(joinType, true)) { // joinType = "|"; // FULL JOIN // } path = path.substring(index + 1); 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(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 !"); } } 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(apijson.JSONRequest.KEY_JOIN + ":" + e.getKey() + " 中 " + key + " 不合法!" + "必须为 &/Table0,> tableSet = tableObj.entrySet(); // 取出所有 join 条件 M requestObj = JSON.createJSONObject(); // (Map) obj.clone(); boolean matchSingle = false; for (Entry tableEntry : tableSet) { String k = tableEntry.getKey(); Object v = k == null ? null : tableEntry.getValue(); if (v == null) { continue; } matchSingle = matchSingle == false && k.equals(key); if (matchSingle) { continue; } if (k.length() > 1 && k.indexOf("@") == k.length() - 1 && v instanceof String) { String sv = (String) v; int ind = sv.endsWith("@") ? -1 : sv.indexOf("/"); if (ind == 0 && key == null) { // 指定了某个就只允许一个 ON 条件 String p = sv.substring(1); int ind2 = p.indexOf("/"); String tk = ind2 < 0 ? null : p.substring(0, ind2); apijson.orm.Entry te = tk == null || p.substring(ind2 + 1).indexOf("/") >= 0 ? null : Pair.parseEntry(tk, true); 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; } } Object rv = getValueByPath(sv); if (rv != null && rv.equals(sv) == false) { requestObj.put(k.substring(0, k.length() - 1), rv); continue; } throw new UnsupportedOperationException(table + "/" + k + " 不合法!" + apijson.JSONRequest.KEY_JOIN + " 关联的 Table 中," + "join: ?/Table/key 时只能有 1 个 key@:value;join: ?/Table 时所有 key@:value 要么是符合 join 格式,要么能直接解析成具体值!"); // TODO 支持 join on } if (k.startsWith("@")) { if (JOIN_COPY_KEY_LIST.contains(k)) { requestObj.put(k, v); // 保留 } } else { if (k.endsWith("@")) { throw new UnsupportedOperationException(table + "/" + k + " 不合法!" + apijson.JSONRequest.KEY_JOIN + " 关联的 Table 中," + "join: ?/Table/key 时只能有 1 个 key@:value;join: ?/Table 时所有 key@:value 要么是符合 join 格式,要么能直接解析成具体值!"); // TODO 支持 join on } if (k.contains("()") == false) { // 不需要远程函数 requestObj.put(k, v); // 保留 } } } Set> refSet = refObj.entrySet(); if (refSet.isEmpty() && "*".equals(joinType) == false) { 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); 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(); String targetPath = (String) refEntry.getValue(); if (StringUtil.isEmpty(targetPath, true)) { throw new IllegalArgumentException(e.getKey() + ":value 中 value 值 " + targetPath + " 不合法!必须为引用赋值的路径 '/targetTable/targetKey' !"); } // 取出引用赋值路径 targetPath 对应的 Table 和 key index = targetPath.lastIndexOf("/"); String targetKey = index < 0 ? null : targetPath.substring(index + 1); if (StringUtil.isName(targetKey) == false) { throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中 targetKey 值 " + targetKey + " 不合法!必须满足英文单词变量名格式!"); } targetPath = targetPath.substring(0, index); index = targetPath.lastIndexOf("/"); String targetTableKey = index < 0 ? targetPath : targetPath.substring(index + 1); // 主表允许别名 apijson.orm.Entry targetEntry = Pair.parseEntry(targetTableKey, true); String targetTable = targetEntry.getKey(); //User if (StringUtil.isName(targetTable) == false) { throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中 targetTable 值 " + targetTable + " 不合法!必须满足大写字母开头的表对象英文单词 key 格式!"); } String targetAlias = targetEntry.getValue(); //owner if (StringUtil.isNotEmpty(targetAlias, true) && StringUtil.isName(targetAlias) == false) { throw new IllegalArgumentException(e.getKey() + ":'/targetTable:targetAlias/targetKey' 中 targetAlias 值 " + targetAlias + " 不合法!必须满足英文单词变量名格式!"); } //targetTable = targetTableKey; // 主表允许别名 if (StringUtil.isName(targetTable) == false) { throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中 targetTable 值 " + targetTable + " 不合法!必须满足大写字母开头的表对象英文单词 key 格式!"); } //对引用的JSONObject添加条件 Map targetObj; try { targetObj = JSON.get(request, targetTableKey); } catch (Exception e2) { 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 !必须是 {} 这种 Map 格式!"); } Join.On on = new Join.On(); on.setKeyAndType(j.getJoinType(), j.getTable(), originKey); if (StringUtil.isName(on.getKey()) == false) { 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)); if (refObj.size() != tableObj.size()) { // 把 key 强制放最前,AbstractSQLExcecutor 中 config.putWhere 也是放尽可能最前 refObj.putAll(tableObj); parentPathObj.put(tableKey, refObj); // tableObj.clear(); // tableObj.putAll(refObj); } // 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 >>>>>>>>> } //拼接多个 SQLConfig 的SQL语句,然后执行,再把结果分别缓存(Moment, User等)到 SQLExecutor 的 cacheMap // AbstractSQLConfig config0 = null; // String sql = "SELECT " + config0.getColumnString() + " FROM " + config0.getTable() + " INNER JOIN " + targetTable + " ON " // + onList.get(0) + config0.getGroupString() + config0.getHavingString() + config0.getOrderString(); return joinList; } /**根据路径取值 * @param parent * @param pathKeys * @return */ 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 (V) parent; } // 逐层到达child的直接容器JSONObject parent Object v = parent; for (int i = 0; i < len; i++) { // 一步一步到达指定位置 if (v == null) { // 不存在或路径错误(中间的key对应value不是JSONObject) break; } String k = getDecodedKey(pathKeys[i]); try { v = getFromObjOrArr(v, k); } catch (Throwable e) { if (IS_PRINT_BIG_LOG) { Log.e(TAG, "getFromObjOrArr failed", e); } v = null; } } return (V) v; } /**获取被依赖引用的key的路径, 实时替换[] -> []/i * @param parentPath * @param valuePath * @return */ public static String getValuePath(String parentPath, String valuePath) { if (valuePath.startsWith("/")) { valuePath = getAbsPath(parentPath, valuePath); } else {//处理[] -> []/i valuePath = replaceArrayChildPath(parentPath, valuePath); } return valuePath; } /**获取绝对路径 * @param path * @param name * @return */ public static String getAbsPath(String path, String name) { Log.i(TAG, "getPath path = " + path + "; name = " + name + " <<<<<<<<<<<<<"); path = StringUtil.get(path); name = StringUtil.get(name); if (StringUtil.isNotEmpty(path, false)) { if (StringUtil.isNotEmpty(name, false)) { path += ((name.startsWith("/") ? "" : "/") + name); } } else { path = name; } if (path.startsWith("/")) { path = path.substring(1); } Log.i(TAG, "getPath return " + path + " >>>>>>>>>>>>>>>>"); return path; } /**替换[] -> []/i * 不能写在getAbsPath里,因为name不一定是依赖路径 * @param parentPath * @param valuePath * @return */ public static String replaceArrayChildPath(String parentPath, String valuePath) { String[] ps = StringUtil.split(parentPath, "]/");//"[]/"); if (ps != null && ps.length > 1) { String[] vs = StringUtil.split(valuePath, "]/"); if (vs != null && vs.length > 0) { String pos; for (int i = 0; i < ps.length - 1; i++) { if (ps[i] == null || ps[i].equals(vs[i]) == false) {//允许""? break; } pos = ps[i+1].contains("/") == false ? ps[i+1] : ps[i+1].substring(0, ps[i+1].indexOf("/")); if ( //StringUtil.isNumer(pos) && vs[i+1].startsWith(pos + "/") == false) { vs[i+1] = pos + "/" + vs[i+1]; } } return StringUtil.get(vs, "]/"); } } return valuePath; } //依赖引用关系 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**将已获取完成的object的内容替换requestObject里对应的值 * @param path object的路径 * @param result 需要被关联的object */ @Override public void putQueryResult(String path, Object result) { Log.i(TAG, "\n putQueryResult valuePath = " + path + "; result = " + result + "\n <<<<<<<<<<<<<<<<<<<<<<<"); // if (queryResultMap.containsKey(valuePath)) {//只保存被关联的value Log.d(TAG, "putQueryResult queryResultMap.containsKey(valuePath) >> queryResultMap.put(path, result);"); queryResultMap.put(path, result); // } } //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 /**根据路径获取值 * @param valuePath -the path need to get value * @return parent == null ? valuePath : parent.get(keys[keys.length - 1]) *

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

*/ @Override public Object getValueByPath(String valuePath) { Log.i(TAG, "<<<<<<<<<<<<<<< \n getValueByPath valuePath = " + valuePath + "\n <<<<<<<<<<<<<<<<<<"); if (StringUtil.isEmpty(valuePath, true)) { Log.e(TAG, "getValueByPath StringUtil.isNotEmpty(valuePath, true) == false >> return null;"); return null; } Object target = queryResultMap.get(valuePath); if (target != null) { return target; } //取出key被valuePath包含的result,再从里面获取key对应的value Object parent = null; String[] keys = null; for (Entry entry : queryResultMap.entrySet()){ String path = entry.getKey(); if (valuePath.startsWith(path + "/")) { try { parent = entry.getValue(); } catch (Exception e) { Log.e(TAG, "getValueByPath try { parent = (Map) queryResultMap.get(path); } catch { " + "\n parent not instanceof Map!"); parent = null; } if (parent != null) { keys = StringUtil.splitPath(valuePath.substring(path.length())); } break; } } target = getValue(parent, keys); // 逐层到达targetKey的直接容器JSONObject parent if (target == null) { //从requestObject中取值 target = getValue(requestObject, StringUtil.splitPath(valuePath)); } Log.i(TAG, "getValueByPath >> getValue >> return target = " + target); return target; } /**解码 引用赋值 路径中的 key,支持把 URL encode 后的值,转为 decode 后的原始值,例如 %2Fuser%2Flist -> /user/list ; %7B%7D -> [] * @param key * @return */ public static String getDecodedKey(String key) { try { return URLDecoder.decode(key, StringUtil.UTF_8); } catch (Throwable e) { return key; } } //依赖引用关系 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> public static final String KEY_CONFIG = "config"; public static final String KEY_SQL = "sql"; protected Map> arrayMainCacheMap = new HashMap<>(); public void putArrayMainCache(String arrayPath, List mainTableDataList) { arrayMainCacheMap.put(arrayPath, mainTableDataList); } public List getArrayMainCache(String arrayPath) { return arrayMainCacheMap.get(arrayPath); } public M getArrayMainCacheItem(String arrayPath, int position) { List list = getArrayMainCache(arrayPath); return list == null || position >= list.size() ? null : list.get(position); } /**执行 SQL 并返回 Map * @param config * @return * @throws Exception */ @Override 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) { M sqlObj = JSON.createJSONObject(); sqlObj.put(KEY_CONFIG, config); return sqlObj;//容易丢失信息 JSON.parseObject(config); } try { M result; boolean explain = config.isExplain(); if (explain) { //如果先执行 explain,则 execute 会死循环,所以只能先执行非 explain config.setExplain(false); //对下面 config.getSQL(false); 生效 M res = getSQLExecutor().execute(config, false); //如果是查询方法,才能执行explain if (RequestMethod.isQueryMethod(config.getMethod()) && config.isElasticsearch() == false){ config.setExplain(explain); Map explainResult = config.isMain() && config.getPosition() != 0 ? null : getSQLExecutor().execute(config, false); if (explainResult == null) { result = res; } else { result = JSON.createJSONObject(); result.put(KEY_EXPLAIN, explainResult); result.putAll(res); } } else {//如果是更新请求,不执行explain,但可以返回sql result = JSON.createJSONObject(); result.put(KEY_SQL, config.gainSQL(false)); result.putAll(res); } } else { result = getSQLExecutor().execute(config, false); // FIXME 改为直接在 sqlExecutor 内加好,最后 Parser 取结果,可以解决并发执行导致内部计算出错 // executedSQLDuration += sqlExecutor.getExecutedSQLDuration() + sqlExecutor.getSqlResultDuration(); } return result; } catch (Exception e) { throw CommonException.wrap(e, config); } finally { if (config.getPosition() == 0 && config.limitSQLCount()) { int maxSQLCount = getMaxSQLCount(); int sqlCount = getSQLExecutor().getExecutedSQLCount(); Log.d(TAG, "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< \n\n\n 已执行 " + sqlCount + "/" + maxSQLCount + " 条 SQL \n\n\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); if (sqlCount > maxSQLCount) { throw new IllegalArgumentException("截至 " + config.getTable() + " 已执行 " + sqlCount + " 条 SQL,数量已超限,必须在 0-" + maxSQLCount + " 内 !"); } } } } //事务处理 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< private int transactionIsolation = Connection.TRANSACTION_NONE; @Override public int getTransactionIsolation() { return transactionIsolation; } @Override public void setTransactionIsolation(int transactionIsolation) { this.transactionIsolation = transactionIsolation; } @Override public void begin(int transactionIsolation) { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<<<<<<<<<<< begin transactionIsolation = " + transactionIsolation + " >>>>>>>>>>>>>>>>>>>>>>> \n\n"); getSQLExecutor().setTransactionIsolation(transactionIsolation); // 不知道 connection 什么时候创建,不能在这里准确控制,getSqlExecutor().begin(transactionIsolation); } @Override public void rollback() throws SQLException { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<<<<<<<<<<< rollback >>>>>>>>>>>>>>>>>>>>>>> \n\n"); getSQLExecutor().rollback(); } @Override public void rollback(Savepoint savepoint) throws SQLException { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<<<<<<<<<<< rollback savepoint " + (savepoint == null ? "" : "!") + "= null >>>>>>>>>>>>>>>>>>>>>>> \n\n"); getSQLExecutor().rollback(savepoint); } @Override public void commit() throws SQLException { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<<<<<<<<<<< commit >>>>>>>>>>>>>>>>>>>>>>> \n\n"); getSQLExecutor().commit(); } @Override public void close() { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<<<<<<<<<<< close >>>>>>>>>>>>>>>>>>>>>>> \n\n"); getSQLExecutor().close(); } /**开始事务 */ protected void onBegin() { // Log.d(TAG, "onBegin >>"); if (RequestMethod.isQueryMethod(requestMethod)) { return; } begin(getTransactionIsolation()); } /**提交事务 */ protected void onCommit() { // Log.d(TAG, "onCommit >>"); // this.sqlExecutor.getTransactionIsolation() 只有json第一次执行才会设置, get请求=0 if (RequestMethod.isQueryMethod(requestMethod) && getSQLExecutor().getTransactionIsolation() == Connection.TRANSACTION_NONE) { return; } try { commit(); } catch (SQLException e) { Log.e(TAG, "onCommit failed", e); } } /**回滚事务 */ protected void onRollback() { // Log.d(TAG, "onRollback >>"); if (RequestMethod.isQueryMethod(requestMethod)) { return; } try { rollback(); } catch (SQLException e1) { Log.e(TAG, "onRollback failed", e1); try { rollback(null); } catch (SQLException e2) { Log.e(TAG, "onRollback with null failed", e2); } } } //事务处理 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> protected void onClose() { // Log.d(TAG, "onClose >>"); close(); verifier = null; sqlExecutor = null; queryResultMap.clear(); 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) { Log.e(TAG, "parseCorrectRequest failed", e); throw 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; } } ================================================ FILE: APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java ================================================ /*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 apijson.orm.exception.UnsupportedDataTypeException; import apijson.orm.model.*; import java.util.*; import java.util.Map.Entry; import java.util.regex.Pattern; 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, L extends List> implements SQLConfig { private static final String TAG = "AbstractSQLConfig"; /** * 为 true 则兼容 5.0 之前 @having:"toId>0;avg(id)<100000" 默认 AND 连接,为 HAVING toId>0 AND avg(id)<100000; * 否则按 5.0+ 新版默认 OR 连接,为 HAVING toId>0 OR avg(id)<100000,使用 @having& 或 @having:{ @combine: null } 时才用 AND 连接 */ public static boolean IS_HAVING_DEFAULT_AND = false; /** * 为 true 则兼容 5.0 之前 @having:"toId>0" 这种不包含 SQL 函数的表达式; * 否则按 5.0+ 新版不允许,可以用 @having:"(toId)>0" 替代 */ public static boolean IS_HAVING_ALLOW_NOT_FUNCTION = false; /** * 开启 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 PREFIX_DISTINCT = "DISTINCT "; public static Pattern PATTERN_SCHEMA; // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! 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; // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL public static Map RAW_MAP; // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL public static Map SQL_AGGREGATE_FUNCTION_MAP; 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)) 通过! // 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); TABLE_KEY_MAP.put(PgAttribute.class.getSimpleName(), PgAttribute.TABLE_NAME); TABLE_KEY_MAP.put(SysTable.class.getSimpleName(), SysTable.TABLE_NAME); TABLE_KEY_MAP.put(SysColumn.class.getSimpleName(), SysColumn.TABLE_NAME); TABLE_KEY_MAP.put(ExtendedProperty.class.getSimpleName(), ExtendedProperty.TABLE_NAME); 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()); CONFIG_TABLE_LIST.add(Request.class.getSimpleName()); CONFIG_TABLE_LIST.add(Access.class.getSimpleName()); CONFIG_TABLE_LIST.add(Document.class.getSimpleName()); CONFIG_TABLE_LIST.add(TestRecord.class.getSimpleName()); DATABASE_LIST = new ArrayList<>(); DATABASE_LIST.add(DATABASE_MYSQL); DATABASE_LIST.add(DATABASE_POSTGRESQL); DATABASE_LIST.add(DATABASE_SQLSERVER); DATABASE_LIST.add(DATABASE_ORACLE); DATABASE_LIST.add(DATABASE_DB2); 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<>(); // 保证顺序,避免配置冲突等意外情况 RAW_MAP.put("+", ""); RAW_MAP.put("-", ""); RAW_MAP.put("*", ""); RAW_MAP.put("/", ""); RAW_MAP.put("=", ""); RAW_MAP.put("!=", ""); RAW_MAP.put(">", ""); RAW_MAP.put(">=", ""); RAW_MAP.put("<", ""); RAW_MAP.put("<=", ""); RAW_MAP.put("%", ""); RAW_MAP.put("(", ""); RAW_MAP.put(")", ""); 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", ""); RAW_MAP.put("IS NULL", ""); RAW_MAP.put("IS", ""); RAW_MAP.put("NULL", ""); RAW_MAP.put("AND", ""); RAW_MAP.put("OR", ""); RAW_MAP.put("NOT", ""); RAW_MAP.put("VALUE", ""); RAW_MAP.put("DISTINCT", ""); RAW_MAP.put("CASE", ""); RAW_MAP.put("WHEN", ""); RAW_MAP.put("THEN", ""); RAW_MAP.put("ELSE", ""); RAW_MAP.put("END", ""); //时间 RAW_MAP.put("now()", ""); RAW_MAP.put("DATE", ""); RAW_MAP.put("TIME", ""); RAW_MAP.put("DATETIME", ""); RAW_MAP.put("TIMESTAMP", ""); RAW_MAP.put("DateTime", ""); RAW_MAP.put("SECOND", ""); RAW_MAP.put("MINUTE", ""); RAW_MAP.put("HOUR", ""); RAW_MAP.put("DAY", ""); RAW_MAP.put("WEEK", ""); RAW_MAP.put("MONTH", ""); RAW_MAP.put("QUARTER", ""); RAW_MAP.put("YEAR", ""); // RAW_MAP.put("json", ""); // RAW_MAP.put("unit", ""); //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED RAW_MAP.put("BINARY", ""); RAW_MAP.put("SIGNED", ""); RAW_MAP.put("DECIMAL", ""); RAW_MAP.put("DOUBLE", ""); RAW_MAP.put("FLOAT", ""); RAW_MAP.put("BOOLEAN", ""); RAW_MAP.put("ENUM", ""); RAW_MAP.put("SET", ""); RAW_MAP.put("POINT", ""); RAW_MAP.put("BLOB", ""); RAW_MAP.put("LONGBLOB", ""); RAW_MAP.put("UNSIGNED", ""); RAW_MAP.put("BIT", ""); RAW_MAP.put("TINYINT", ""); RAW_MAP.put("SMALLINT", ""); RAW_MAP.put("INT", ""); RAW_MAP.put("BIGINT", ""); RAW_MAP.put("CHAR", ""); RAW_MAP.put("VARCHAR", ""); RAW_MAP.put("TEXT", ""); RAW_MAP.put("LONGTEXT", ""); RAW_MAP.put("JSON", ""); //窗口函数关键字 RAW_MAP.put("OVER", ""); RAW_MAP.put("INTERVAL", ""); RAW_MAP.put("GROUP BY", ""); // 往前 RAW_MAP.put("GROUP", ""); // 往前 RAW_MAP.put("ORDER BY", ""); // 往前 RAW_MAP.put("ORDER", ""); RAW_MAP.put("PARTITION BY", ""); // 往前 RAW_MAP.put("PARTITION", ""); // 往前 RAW_MAP.put("BY", ""); RAW_MAP.put("DESC", ""); RAW_MAP.put("ASC", ""); RAW_MAP.put("PRECEDING", ""); // 往前 RAW_MAP.put("FOLLOWING", ""); // 往后 RAW_MAP.put("BETWEEN", ""); 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("NATURAL", ""); RAW_MAP.put("LANGUAGE", ""); RAW_MAP.put("MODE", ""); SQL_AGGREGATE_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 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,如果 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(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_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("isValidUTF8", ""); // 检查字符串是否为有效的UTF-8编码,是则返回1,否则返回0。 SQL_FUNCTION_MAP.put("toValidUTF8", ""); // 用(U+FFFD)字符替换无效的UTF-8字符。所有连续的无效字符都会被替换为一个替换字符。 SQL_FUNCTION_MAP.put("reverseUTF8", ""); // 以Unicode字符为单位反转UTF-8编码的字符串。 SQL_FUNCTION_MAP.put("concatAssumeInjective", ""); // concatAssumeInjective(s1, s2, …) 与concat相同,区别在于,你需要保证concat(s1, s2, s3) -> s4是单射的,它将用于GROUP BY的优化。 SQL_FUNCTION_MAP.put("substringUTF8", ""); // substringUTF8(s,offset,length)¶ 与’substring’相同,但其操作单位为Unicode字符,函数假设字符串是以UTF-8进行编码的文本。如果不是则可能返回一个预期外的结果(不会抛出异常)。 SQL_FUNCTION_MAP.put("appendTrailingCharIfAbsent", ""); // appendTrailingCharIfAbsent(s,c) 如果’s’字符串非空并且末尾不包含’c’字符,则将’c’字符附加到末尾 SQL_FUNCTION_MAP.put("convertCharset", ""); // convertCharset(s,from,to) 返回从’from’中的编码转换为’to’中的编码的字符串’s’。 SQL_FUNCTION_MAP.put("base64Encode", ""); // base64Encode(s) 将字符串’s’编码成base64 SQL_FUNCTION_MAP.put("base64Decode", ""); // base64Decode(s) 使用base64将字符串解码成原始字符串。如果失败则抛出异常。 SQL_FUNCTION_MAP.put("tryBase64Decode", ""); // tryBase64Decode(s) 使用base64将字符串解码成原始字符串。但如果出现错误,将返回空字符串。 SQL_FUNCTION_MAP.put("endsWith", ""); // endsWith(s,后缀) 返回是否以指定的后缀结尾。如果字符串以指定的后缀结束,则返回1,否则返回0。 SQL_FUNCTION_MAP.put("startsWith", ""); // startsWith(s,前缀) 返回是否以指定的前缀开头。如果字符串以指定的前缀开头,则返回1,否则返回0。 SQL_FUNCTION_MAP.put("trimLeft", ""); // trimLeft(s)返回一个字符串,用于删除左侧的空白字符。 SQL_FUNCTION_MAP.put("trimRight", ""); // trimRight(s) 返回一个字符串,用于删除右侧的空白字符。 SQL_FUNCTION_MAP.put("trimBoth", ""); // trimBoth(s),用于删除任一侧的空白字符 SQL_FUNCTION_MAP.put("extractAllGroups", ""); // extractAllGroups(text, regexp) 从正则表达式匹配的非重叠子字符串中提取所有组 // SQL_FUNCTION_MAP.put("leftPad", ""); // leftPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度 // SQL_FUNCTION_MAP.put("leftPadUTF8", ""); // leftPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度 // SQL_FUNCTION_MAP.put("rightPad", ""); // rightPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度 // SQL_FUNCTION_MAP.put("rightPadUTF8", "");// rightPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度。 SQL_FUNCTION_MAP.put("normalizeQuery", ""); // normalizeQuery(x) 用占位符替换文字、文字序列和复杂的别名。 SQL_FUNCTION_MAP.put("normalizedQueryHash", ""); // normalizedQueryHash(x) 为类似查询返回相同的64位散列值,但不包含文字值。有助于对查询日志进行分析 SQL_FUNCTION_MAP.put("positionUTF8", ""); // positionUTF8(s, needle[, start_pos]) 返回在字符串中找到的子字符串的位置(以Unicode点表示),从1开始。 SQL_FUNCTION_MAP.put("multiSearchFirstIndex", ""); // multiSearchFirstIndex(s, [needle1, needle2, …, needlen]) 返回字符串s中最左边的needlei的索引i(从1开始),否则返回0 SQL_FUNCTION_MAP.put("multiSearchAny", ""); // multiSearchAny(s, [needle1, needle2, …, needlen])如果至少有一个字符串needlei匹配字符串s,则返回1,否则返回0。 SQL_FUNCTION_MAP.put("match", ""); // match(s, pattern) 检查字符串是否与模式正则表达式匹配。re2正则表达式。re2正则表达式的语法比Perl正则表达式的语法更有局限性。 SQL_FUNCTION_MAP.put("multiMatchAny", ""); // multiMatchAny(s, [pattern1, pattern2, …, patternn]) 与match相同,但是如果没有匹配的正则表达式返回0,如果有匹配的模式返回1 SQL_FUNCTION_MAP.put("multiMatchAnyIndex", ""); // multiMatchAnyIndex(s, [pattern1, pattern2, …, patternn]) 与multiMatchAny相同,但返回与干堆匹配的任何索引 SQL_FUNCTION_MAP.put("extract", ""); // extract(s, pattern) 使用正则表达式提取字符串的片段 SQL_FUNCTION_MAP.put("extractAll", ""); // extractAll(s, pattern) 使用正则表达式提取字符串的所有片段 SQL_FUNCTION_MAP.put("like", ""); // like(s, pattern) 检查字符串是否与简单正则表达式匹配 SQL_FUNCTION_MAP.put("notLike", ""); // 和‘like’是一样的,但是是否定的 SQL_FUNCTION_MAP.put("countSubstrings", ""); // countSubstrings(s, needle[, start_pos])返回子字符串出现的次数 SQL_FUNCTION_MAP.put("countMatches", ""); // 返回干s中的正则表达式匹配数。countMatches(s, pattern) SQL_FUNCTION_MAP.put("replaceOne", ""); // replaceOne(s, pattern, replacement)将' s '中的' pattern '子串的第一个出现替换为' replacement '子串。 SQL_FUNCTION_MAP.put("replaceAll", ""); // replaceAll(s, pattern, replacement)/用' replacement '子串替换' s '中所有出现的' pattern '子串 SQL_FUNCTION_MAP.put("replaceRegexpOne", ""); // replaceRegexpOne(s, pattern, replacement)使用' pattern '正则表达式进行替换 SQL_FUNCTION_MAP.put("replaceRegexpAll", ""); // replaceRegexpAll(s, pattern, replacement) SQL_FUNCTION_MAP.put("regexpQuoteMeta", ""); // regexpQuoteMeta(s)该函数在字符串中某些预定义字符之前添加一个反斜杠 // clickhouse日期函数 SQL_FUNCTION_MAP.put("toYear", ""); // 将Date或DateTime转换为包含年份编号(AD)的UInt16类型的数字。 SQL_FUNCTION_MAP.put("toQuarter", ""); // 将Date或DateTime转换为包含季度编号的UInt8类型的数字。 SQL_FUNCTION_MAP.put("toMonth", ""); // Date或DateTime转换为包含月份编号(1-12)的UInt8类型的数字。 SQL_FUNCTION_MAP.put("toDayOfYear", ""); // 将Date或DateTime转换为包含一年中的某一天的编号的UInt16(1-366)类型的数字。 SQL_FUNCTION_MAP.put("toDayOfMonth", "");// 将Date或DateTime转换为包含一月中的某一天的编号的UInt8(1-31)类型的数字。 SQL_FUNCTION_MAP.put("toDayOfWeek", ""); // 将Date或DateTime转换为包含一周中的某一天的编号的UInt8(周一是1, 周日是7)类型的数字。 SQL_FUNCTION_MAP.put("toHour", ""); // 将DateTime转换为包含24小时制(0-23)小时数的UInt8数字。 SQL_FUNCTION_MAP.put("toMinute", ""); // 将DateTime转换为包含一小时中分钟数(0-59)的UInt8数字。 SQL_FUNCTION_MAP.put("toSecond", ""); // 将DateTime转换为包含一分钟中秒数(0-59)的UInt8数字。 SQL_FUNCTION_MAP.put("toUnixTimestamp", ""); // 对于DateTime参数:将值转换为UInt32类型的数字-Unix时间戳 SQL_FUNCTION_MAP.put("toStartOfYear", ""); // 将Date或DateTime向前取整到本年的第一天。 SQL_FUNCTION_MAP.put("toStartOfISOYear", ""); // 将Date或DateTime向前取整到ISO本年的第一天。 SQL_FUNCTION_MAP.put("toStartOfQuarter", "");// 将Date或DateTime向前取整到本季度的第一天。 SQL_FUNCTION_MAP.put("toStartOfMonth", ""); // 将Date或DateTime向前取整到本月的第一天。 SQL_FUNCTION_MAP.put("toMonday", ""); // 将Date或DateTime向前取整到本周的星期 SQL_FUNCTION_MAP.put("toStartOfWeek", ""); // 按mode将Date或DateTime向前取整到最近的星期日或星期一。 SQL_FUNCTION_MAP.put("toStartOfDay", ""); // 将DateTime向前取整到今天的开始。 SQL_FUNCTION_MAP.put("toStartOfHour", ""); // 将DateTime向前取整到当前小时的开始。 SQL_FUNCTION_MAP.put("toStartOfMinute", ""); // 将DateTime向前取整到当前分钟的开始。 SQL_FUNCTION_MAP.put("toStartOfSecond", ""); // 将DateTime向前取整到当前秒数的开始。 SQL_FUNCTION_MAP.put("toStartOfFiveMinute", "");// 将DateTime以五分钟为单位向前取整到最接近的时间点。 SQL_FUNCTION_MAP.put("toStartOfTenMinutes", ""); // 将DateTime以十分钟为单位向前取整到最接近的时间点。 SQL_FUNCTION_MAP.put("toStartOfFifteenMinutes", ""); // 将DateTime以十五分钟为单位向前取整到最接近的时间点。 SQL_FUNCTION_MAP.put("toStartOfInterval", ""); // SQL_FUNCTION_MAP.put("toTime", ""); // 将DateTime中的日期转换为一个固定的日期,同时保留时间部分。 SQL_FUNCTION_MAP.put("toISOYear", ""); // 将Date或DateTime转换为包含ISO年份的UInt16类型的编号。 SQL_FUNCTION_MAP.put("toISOWeek", ""); // SQL_FUNCTION_MAP.put("toWeek", "");// 返回Date或DateTime的周数。 SQL_FUNCTION_MAP.put("toYearWeek", ""); // 返回年和周的日期 SQL_FUNCTION_MAP.put("date_trunc", ""); // 截断日期和时间数据到日期的指定部分 SQL_FUNCTION_MAP.put("date_diff", ""); // 回两个日期或带有时间值的日期之间的差值。 SQL_FUNCTION_MAP.put("yesterday", ""); // 不接受任何参数并在请求执行时的某一刻返回昨天的日期(Date)。 SQL_FUNCTION_MAP.put("today", ""); // 不接受任何参数并在请求执行时的某一刻返回当前日期(Date)。 SQL_FUNCTION_MAP.put("timeSlot", ""); // 将时间向前取整半小时。 SQL_FUNCTION_MAP.put("toYYYYMM", ""); // SQL_FUNCTION_MAP.put("toYYYYMMDD", "");// SQL_FUNCTION_MAP.put("toYYYYMMDDhhmmss", ""); // SQL_FUNCTION_MAP.put("addYears", ""); // Function adds a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime SQL_FUNCTION_MAP.put("addMonths", ""); // 同上 SQL_FUNCTION_MAP.put("addWeeks", ""); // 同上 SQL_FUNCTION_MAP.put("addDays", ""); // 同上 SQL_FUNCTION_MAP.put("addHours", ""); // 同上 SQL_FUNCTION_MAP.put("addMinutes", "");// 同上 SQL_FUNCTION_MAP.put("addSeconds", ""); // 同上 SQL_FUNCTION_MAP.put("addQuarters", ""); // 同上 SQL_FUNCTION_MAP.put("subtractYears", ""); // Function subtract a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime SQL_FUNCTION_MAP.put("subtractMonths", ""); // 同上 SQL_FUNCTION_MAP.put("subtractWeeks", ""); // 同上 SQL_FUNCTION_MAP.put("subtractDays", ""); // 同上 SQL_FUNCTION_MAP.put("subtractours", "");// 同上 SQL_FUNCTION_MAP.put("subtractMinutes", ""); // 同上 SQL_FUNCTION_MAP.put("subtractSeconds", ""); // 同上 SQL_FUNCTION_MAP.put("subtractQuarters", ""); // 同上 SQL_FUNCTION_MAP.put("formatDateTime", ""); // 函数根据给定的格式字符串来格式化时间 SQL_FUNCTION_MAP.put("timestamp_add", ""); // 使用提供的日期或日期时间值添加指定的时间值。 SQL_FUNCTION_MAP.put("timestamp_sub", ""); // 从提供的日期或带时间的日期中减去时间间隔。 // ClickHouse json函数 SQL_FUNCTION_MAP.put("visitParamHas", ""); // visitParamHas(params, name)检查是否存在«name»名称的字段 SQL_FUNCTION_MAP.put("visitParamExtractUInt", ""); // visitParamExtractUInt(params, name)将名为«name»的字段的值解析成UInt64。 SQL_FUNCTION_MAP.put("visitParamExtractInt", ""); // 与visitParamExtractUInt相同,但返回Int64。 SQL_FUNCTION_MAP.put("visitParamExtractFloat", ""); // 与visitParamExtractUInt相同,但返回Float64。 SQL_FUNCTION_MAP.put("visitParamExtractBool", "");// 解析true/false值。其结果是UInt8类型的。 SQL_FUNCTION_MAP.put("visitParamExtractRaw", ""); // 返回字段的值,包含空格符。 SQL_FUNCTION_MAP.put("visitParamExtractString", ""); // 使用双引号解析字符串。这个值没有进行转义。如果转义失败,它将返回一个空白字符串。 SQL_FUNCTION_MAP.put("JSONHas", ""); // 如果JSON中存在该值,则返回1。 SQL_FUNCTION_MAP.put("JSONLength", ""); // 返回JSON数组或JSON对象的长度。 SQL_FUNCTION_MAP.put("JSONType", ""); // 返回JSON值的类型。 SQL_FUNCTION_MAP.put("JSONExtractUInt", ""); // 解析JSON并提取值。这些函数类似于visitParam*函数。 SQL_FUNCTION_MAP.put("JSONExtractInt", ""); // SQL_FUNCTION_MAP.put("JSONExtractFloat", ""); // SQL_FUNCTION_MAP.put("JSONExtractBool", ""); // SQL_FUNCTION_MAP.put("JSONExtractString", ""); // 解析JSON并提取字符串。此函数类似于visitParamExtractString函数。 SQL_FUNCTION_MAP.put("JSONExtract", ""); // 解析JSON并提取给定ClickHouse数据类型的值。 SQL_FUNCTION_MAP.put("JSONExtractKeysAndValues", ""); // 从JSON中解析键值对,其中值是给定的ClickHouse数据类型 SQL_FUNCTION_MAP.put("JSONExtractRaw", ""); // 返回JSON的部分。 SQL_FUNCTION_MAP.put("toJSONString", ""); // // ClickHouse 类型转换函数 SQL_FUNCTION_MAP.put("toInt8", ""); // toInt8(expr) 转换一个输入值为Int类型 SQL_FUNCTION_MAP.put("toInt16", ""); SQL_FUNCTION_MAP.put("toInt32", ""); SQL_FUNCTION_MAP.put("toInt64", ""); SQL_FUNCTION_MAP.put("toInt8OrZero", ""); // toInt(8|16|32|64)OrZero 尝试把字符串转为 Int 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)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("toUInt16", ""); SQL_FUNCTION_MAP.put("toUInt32", ""); SQL_FUNCTION_MAP.put("toUInt64", ""); 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 SQL_FUNCTION_MAP.put("toUInt16OrNull", ""); SQL_FUNCTION_MAP.put("toUInt32OrNull", ""); SQL_FUNCTION_MAP.put("toUInt64OrNull", ""); SQL_FUNCTION_MAP.put("toFloat32", ""); SQL_FUNCTION_MAP.put("toFloat64", ""); SQL_FUNCTION_MAP.put("toFloat32OrZero", ""); SQL_FUNCTION_MAP.put("toFloat64OrZero", ""); SQL_FUNCTION_MAP.put("toFloat32OrNull", ""); SQL_FUNCTION_MAP.put("toFloat64OrNull", ""); SQL_FUNCTION_MAP.put("toDate", ""); // SQL_FUNCTION_MAP.put("toDateOrZero", ""); // toInt16(expr) SQL_FUNCTION_MAP.put("toDateOrNull", ""); // toInt32(expr) SQL_FUNCTION_MAP.put("toDateTimeOrZero", ""); // toInt64(expr) 尝试把字符串转为 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("toDecimal256", ""); SQL_FUNCTION_MAP.put("toDecimal32OrNull", ""); SQL_FUNCTION_MAP.put("toDecimal64OrNull", ""); SQL_FUNCTION_MAP.put("toDecimal128OrNull", ""); SQL_FUNCTION_MAP.put("toDecimal256OrNull", ""); SQL_FUNCTION_MAP.put("toDecimal32OrZero", ""); SQL_FUNCTION_MAP.put("toDecimal64OrZero", ""); SQL_FUNCTION_MAP.put("toDecimal128OrZero", ""); SQL_FUNCTION_MAP.put("toDecimal256OrZero", ""); SQL_FUNCTION_MAP.put("toIntervalSecond", ""); // 把一个数值类型的值转换为Interval类型的数据。 SQL_FUNCTION_MAP.put("toIntervalMinute", ""); SQL_FUNCTION_MAP.put("toIntervalHour", ""); SQL_FUNCTION_MAP.put("toIntervalDay", ""); SQL_FUNCTION_MAP.put("toIntervalWeek", ""); SQL_FUNCTION_MAP.put("toIntervalMonth", ""); SQL_FUNCTION_MAP.put("toIntervalQuarter", ""); SQL_FUNCTION_MAP.put("toIntervalYear", ""); SQL_FUNCTION_MAP.put("parseDateTimeBestEffort", ""); // 把String类型的时间日期转换为DateTime数据类型。 SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrNull", ""); SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrZero", ""); SQL_FUNCTION_MAP.put("toLowCardinality", ""); // ClickHouse hash 函数 SQL_FUNCTION_MAP.put("halfMD5", ""); // 计算字符串的MD5。然后获取结果的前8个字节并将它们作为UInt64(大端)返回 SQL_FUNCTION_MAP.put("MD5", ""); // 计算字符串的MD5并将结果放入FixedString(16)中返回 // ClickHouse ip 地址函数 SQL_FUNCTION_MAP.put("IPv4NumToString", ""); // 接受一个 UInt32(大端)表示的IPv4的地址,返回相应IPv4的字符串表现形式 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,包含删除指定位之后的地址的文本格 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", ""); // 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("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 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 @Override public String getIdKey() { return KEY_ID; } @NotNull @Override public String getUserIdKey() { return KEY_USER_ID; } private RequestMethod method; //操作方法 private boolean prepared = true; //预编译 private boolean main = true; private Object id; // Table 的 id private Object idIn; // User Table 的 id IN private Object userId; // Table 的 userId private Object userIdIn; // Table 的 userId IN /** * TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"}) */ 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; //表名 private String alias; //表别名 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 List column; //表内字段名(或函数名,仅查询操作可用)的字符串数组,','分隔 private List> values; //对应表内字段的值的字符串数组,','分隔 private List nulls; private Map cast; private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() private Map where; //筛选条件,key:value形式 private String combine; //条件组合, a | (b & c & !(d | !e)) private Map> combineMap; //条件组合,{ "&":[key], "|":[key], "!":[key] } //array item <<<<<<<<<< private int count; //Table数量 private int page; //Table所在页码 private int position; //Table在[]中的位置 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; //连表 配置列表 //array item >>>>>>>>>> private boolean test; //测试 private String procedure; public AbstractSQLConfig setProcedure(String procedure) { this.procedure = procedure; return this; } public String getProcedure() { return procedure; } public AbstractSQLConfig(RequestMethod method) { setMethod(method); } public AbstractSQLConfig(RequestMethod method, String table) { this(method); setTable(table); } public AbstractSQLConfig(RequestMethod method, int count, int page) { this(method); setCount(count); setPage(page); } @NotNull @Override public RequestMethod getMethod() { if (method == null) { method = GET; } return method; } @Override public AbstractSQLConfig setMethod(RequestMethod method) { this.method = method; return this; } @Override public boolean isPrepared() { return prepared && ! isMongoDB(); // MongoDB JDBC 还不支持预编译; } @Override public AbstractSQLConfig setPrepared(boolean prepared) { this.prepared = prepared; return this; } @Override public boolean isMain() { return main; } @Override public AbstractSQLConfig setMain(boolean main) { this.main = main; return this; } @Override public Object getId() { return id; } @Override public AbstractSQLConfig setId(Object id) { this.id = id; return this; } @Override public Object getIdIn() { return idIn; } @Override public AbstractSQLConfig setIdIn(Object idIn) { this.idIn = idIn; return this; } @Override public Object getUserId() { return userId; } @Override public AbstractSQLConfig setUserId(Object userId) { this.userId = userId; return this; } @Override public Object getUserIdIn() { return userIdIn; } @Override public AbstractSQLConfig setUserIdIn(Object userIdIn) { this.userIdIn = userIdIn; return this; } @Override public String getRole() { //不能 @NotNull , AbstractParser#getSQLObject 内当getRole() == null时填充默认值 return role; } @Override public AbstractSQLConfig setRole(String role) { this.role = role; return this; } @Override public boolean isDistinct() { return distinct; } @Override public AbstractSQLConfig setDistinct(boolean distinct) { this.distinct = distinct; return this; } @Override public String getDatabase() { return database; } @Override public AbstractSQLConfig setDatabase(String database) { this.database = database; return this; } /** * @return db == null ? DEFAULT_DATABASE : db */ @NotNull 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(gainSQLDatabase()); } public static boolean isMySQL(String db) { return DATABASE_MYSQL.equals(db); } @Override public boolean isPostgreSQL() { return isPostgreSQL(gainSQLDatabase()); } public static boolean isPostgreSQL(String db) { return DATABASE_POSTGRESQL.equals(db); } @Override public boolean isSQLServer() { return isSQLServer(gainSQLDatabase()); } public static boolean isSQLServer(String db) { return DATABASE_SQLSERVER.equals(db); } @Override public boolean isOracle() { return isOracle(gainSQLDatabase()); } public static boolean isOracle(String db) { return DATABASE_ORACLE.equals(db); } @Override public boolean isDb2() { 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(gainSQLDatabase()); } public static boolean isClickHouse(String db) { return DATABASE_CLICKHOUSE.equals(db); } @Override public boolean isHive() { return isHive(gainSQLDatabase()); } public static boolean isHive(String db) { return DATABASE_HIVE.equals(db); } @Override public boolean isPresto() { return isPresto(gainSQLDatabase()); } public static boolean isPresto(String db) { return DATABASE_PRESTO.equals(db); } @Override public boolean isTrino() { return isTrino(gainSQLDatabase()); } 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 @Override public String gainSQLSchema() { String table = getTable(); // FIXME 全部默认填充判断是 系统表 则不填充 // 强制,避免因为全局默认的 @schema 自动填充进来,导致这几个类的 schema 为 sys 等其它值 if (Table.TAG.equals(table) || Column.TAG.equals(table)) { return SCHEMA_INFORMATION; //MySQL, PostgreSQL, SQL Server 都有的 } if (PgClass.TAG.equals(table) || PgAttribute.TAG.equals(table)) { return ""; //PostgreSQL 的 pg_class 和 pg_attribute 表好像不属于任何 Schema } if (SysTable.TAG.equals(table) || SysColumn.TAG.equals(table) || ExtendedProperty.TAG.equals(table)) { return SCHEMA_SYS; //SQL Server 在 sys 中的属性比 information_schema 中的要全,能拿到注释 } 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(); // 前端传参 @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) { if (schema != null) { AbstractFunctionParser.verifySchema(schema, getTable()); } this.schema = schema; return this; } @Override public String getDatasource() { return datasource; } @Override public AbstractSQLConfig setDatasource(String datasource) { this.datasource = datasource; return this; } /**请求传进来的Table名 * @return * @see {@link #gainSQLTable()} */ @Override public String getTable() { return table; } /**数据库里的真实Table名 * 通过 {@link #TABLE_KEY_MAP} 映射 * @return */ @Override 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; } @Override public String gainTablePath() { String q = getQuote(); 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; 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 必须是 数字或英语字母组合!并且不要有多余的空格!"); } } keys[i] = gainKey(origin); } return (hasPrefix ? " SAMPLE BY " : "") + StringUtil.concat(StringUtil.get(keys), joinSample, ", "); } @Override public String getLatest() { return latest; } public AbstractSQLConfig setLatest(String... conditions) { return setLatest(StringUtil.get(conditions)); } @Override public AbstractSQLConfig setLatest(String latest) { this.latest = latest; return this; } public String gainLatestString(boolean hasPrefix) { //加上子表的 latest String joinLatest = ""; if (joinList != null) { boolean first = true; for (Join join : joinList) { if (join.isAppJoin()) { continue; } 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).gainLatestString(false); if (StringUtil.isNotEmpty(c, true)) { joinLatest += (first ? "" : ", ") + c; first = false; } } } } String latest = StringUtil.trim(getLatest()); String[] keys = StringUtil.split(latest); if (keys == null || keys.length <= 0) { return StringUtil.isEmpty(joinLatest, true) ? "" : (hasPrefix ? " LATEST ON " : "") + joinLatest; } for (int i = 0; i < keys.length; i++) { 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] = gainKey(origin); } return (hasPrefix ? " LATEST ON " : "") + StringUtil.concat(StringUtil.get(keys), joinLatest, ", "); } @Override public String getPartition() { return partition; } public AbstractSQLConfig setPartition(String... conditions) { return setPartition(StringUtil.get(conditions)); } @Override public AbstractSQLConfig setPartition(String partition) { this.partition = partition; return this; } public String gainPartitionString(boolean hasPrefix) { //加上子表的 partition String joinPartition = ""; if (joinList != null) { boolean first = true; for (Join join : joinList) { if (join.isAppJoin()) { continue; } 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).gainPartitionString(false); if (StringUtil.isNotEmpty(c, true)) { joinPartition += (first ? "" : ", ") + c; first = false; } } } } 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; } for (int i = 0; i < keys.length; i++) { 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("预编译模式下 @partition:value 中 " + item + " 不合法! value 里面用 , 分割的" + "每一项必须是 column 且其中 column 必须是 英语单词!并且不要有多余的空格!"); } } keys[i] = gainKey(origin); } return (hasPrefix ? " PARTITION BY " : "") + StringUtil.concat(StringUtil.get(keys), joinPartition, ", "); } @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; } } } } String fill = StringUtil.trim(getFill()); String[] keys = StringUtil.split(fill); if (keys == null || keys.length <= 0) { return StringUtil.isEmpty(joinFill, true) ? "" : (hasPrefix ? " FILL(" : "") + joinFill + ")"; } 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; } String origin = item; 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 必须是 数字或英语字母组合!并且不要有多余的空格!"); } } keys[i] = gainKey(origin); } return (hasPrefix ? " FILL(" : "") + StringUtil.concat(StringUtil.get(keys), joinFill, ", ") + ")"; } @Override public String getOrder() { return order; } public AbstractSQLConfig setOrder(String... conditions) { return setOrder(StringUtil.get(conditions)); } @Override public AbstractSQLConfig setOrder(String order) { this.order = order; return this; } public String gainOrderString(boolean hasPrefix) { //加上子表的 order String joinOrder = ""; String joinOuterOrder = ""; if (joinList != null) { boolean first = true; for (Join join : joinList) { if (join.isAppJoin()) { continue; } 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).gainOrderString(false); if (StringUtil.isNotEmpty(c, true)) { joinOrder += (first ? "" : ", ") + c; first = false; } } } } 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关键字 // String[] ss = StringUtil.split(order); if (StringUtil.isEmpty(order, true)) { //SQL Server 子查询内必须指定 OFFSET 才能用 ORDER BY String idKey = getIdKey(); if (StringUtil.isEmpty(idKey, true)) { idKey = "id"; // ORDER BY NULL 不行,SQL Server 会报错,必须要有排序,才能使用 OFFSET FETCH,如果没有 idKey,请求中指定 @order 即可 } order = idKey; //让数据库调控默认升序还是降序 + "+"; } //不用这么全面,毕竟没有语法问题还浪费性能,如果有其它问题,让前端传的 JSON 直接加上 @order 来解决 // boolean contains = false; // if (ss != null) { // for (String s : ss) { // if (s != null && s.startsWith(idKey)) { // s = s.substring(idKey.length()); // if ("+".equals(s) || "-".equals(s)) {// || " ASC ".equals(s) || " DESC ".equals(s)) { // contains = true; // break; // } // } // } // } // if (contains == false) { // order = (ss == null || ss.length <= 0 ? "" : order + ",") + idKey + "+"; // } } String[] keys = StringUtil.split(order); if (keys == null || keys.length <= 0) { return StringUtil.isEmpty(joinOrder, true) ? "" : (hasPrefix ? " ORDER BY " : "") + joinOrder; } for (int i = 0; i < keys.length; i++) { String item = keys[i]; if ("rand()".equals(item)) { continue; } int index = item.endsWith("+") ? item.length() - 1 : -1; //StringUtil.split返回数组中,子项不会有null String sort; if (index < 0) { index = item.endsWith("-") ? item.length() - 1 : -1; sort = index <= 0 ? "" : " DESC "; } else { sort = " ASC "; } String origin = index < 0 ? item : item.substring(0, index); if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 if (StringUtil.isName(origin) == false) { throw new IllegalArgumentException("预编译模式下 @order:value 中 " + item + " 不合法! value 里面用 , 分割的" + "每一项必须是 随机函数 rand() 或 column+ / column- 且其中 column 必须是 1 个单词!并且不要有多余的空格!"); } } keys[i] = gainKey(origin) + sort; } 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 public List getRaw() { return raw; } @Override public AbstractSQLConfig setRaw(List raw) { this.raw = raw; return this; } /**获取原始 SQL 片段 * @param key * @param value * @return * @throws Exception */ @Override public String gainRawSQL(String key, Object value) throws Exception { return gainRawSQL(key, value, ! ALLOW_MISSING_KEY_4_COMBINE); } /**获取原始 SQL 片段 * @param key * @param value * @param throwWhenMissing * @return * @throws Exception */ @Override public String gainRawSQL(String key, Object value, boolean throwWhenMissing) throws Exception { if (value == null) { return null; } List rawList = getRaw(); boolean containRaw = rawList != null && rawList.contains(key); if (containRaw && value instanceof String == false) { throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + "对应的 " + key + ":value 中 value 类型只能为 String!"); } String rawSQL = containRaw ? RAW_MAP.get(value) : null; if (containRaw) { if (rawSQL == null) { if (throwWhenMissing) { 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; } } return rawSQL; } @Override public List getJson() { return json; } @Override public AbstractSQLConfig setJson(List json) { this.json = json; return this; } @Override public Subquery getFrom() { return from; } @Override public AbstractSQLConfig setFrom(Subquery from) { this.from = from; return this; } @Override public List getColumn() { return column; } @Override public AbstractSQLConfig setColumn(List column) { this.column = column; return this; } public String gainColumnString() throws Exception { return gainColumnString(false); } public String gainColumnString(boolean inSQLJoin) throws Exception { List column = getColumn(); String as = gainAs(); String q = getQuote(); switch (getMethod()) { case HEAD: case HEADS: //StringUtil.isEmpty(column, true) || column.contains(",") 时SQL.count(column)会return "*" if (isPrepared() && column != null) { List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_COLUMN); for (String c : column) { if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 //排除@raw中的值,以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错 //column.remove(c); continue; } } int index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null String origin = index < 0 ? c : c.substring(0, index); String alias = index < 0 ? null : c.substring(index + 1); if (alias != null && StringUtil.isName(alias) == false) { throw new IllegalArgumentException("HEAD请求: 字符 " + alias + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); } if (StringUtil.isName(origin) == false) { int start = origin.indexOf("("); if (start < 0 || origin.lastIndexOf(")") <= start) { throw new IllegalArgumentException("HEAD请求: 字符" + origin + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + " column:alias 中 column 必须是1个单词!" + "如果有alias,则 alias 也必须为1个单词!并且不要有多余的空格!"); } if (start > 0 && StringUtil.isName(origin.substring(0, start)) == false) { throw new IllegalArgumentException("HEAD请求: 字符 " + origin.substring(0, start) + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); } } } } boolean onlyOne = column != null && column.size() == 1; String c0 = onlyOne ? column.get(0) : null; if (onlyOne) { int index = c0 == null ? -1 : c0.lastIndexOf(":"); if (index > 0) { c0 = c0.substring(0, index); } int start = c0 == null ? -1 : c0.indexOf("("); int end = start <= 0 ? -1 : c0.lastIndexOf(")"); if (start > 0 && end > start) { String fun = c0.substring(0, start); // Invalid use of group function SELECT count(max(`id`)) AS count FROM `sys`.`Comment` if (SQL_AGGREGATE_FUNCTION_MAP.containsKey(fun)) { String group = getGroup(); // TODO 唯一 100% 兼容的可能只有 SELECT count(*) FROM (原语句) AS table return StringUtil.isEmpty(group, true) ? "1" : "count(DISTINCT " + group + ")"; } String[] args = start == end - 1 ? null : StringUtil.split(c0.substring(start + 1, end)); if (args != null && args.length > 0) { List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_COLUMN); c0 = parseSQLExpression(KEY_COLUMN, c0, containRaw, false, null); } return "count(" + c0 + ")" + as + q + JSONResponse.KEY_COUNT + q; } } 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()) { throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); } String s = ""; boolean pfirst = true; for (String c : column) { if (isPrepared() && StringUtil.isName(c) == false) { // 不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! throw new IllegalArgumentException("POST请求: 每一个 key:value 中的key都必须是1个单词!"); } s += ((pfirst ? "" : ",") + gainKey(c)); pfirst = false; } return "(" + s + ")"; case GET: case GETS: String joinColumn = ""; if (joinList != null) { boolean first = true; for (Join join : joinList) { if (join.isAppJoin()) { continue; } SQLConfig ocfg = join.getOnConfig(); boolean isEmpty = ocfg == null || ocfg.getColumn() == null; boolean isLeftOrRightJoin = join.isLeftOrRightJoin(); if (isEmpty && isLeftOrRightJoin) { // 改为 SELECT ViceTable.* 解决 SELECT sum(ViceTable.id) // LEFT/RIGHT JOIN (SELECT sum(id) FROM ViceTable...) AS ViceTable // 不仅导致 SQL 函数重复计算,还有时导致 SQL 报错或对应字段未返回 joinColumn += (first ? "" : ", ") + q + SQLConfig.gainSQLAlias(join.getTable(), join.getAlias()) + q + ".*"; first = false; } else { 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()); //} String c = ((AbstractSQLConfig) cfg).gainColumnString(true); if (StringUtil.isNotEmpty(c, true)) { joinColumn += (first ? "" : ", ") + c; first = false; } } } inSQLJoin = true; } } 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() ? (noColumn ? "" : tableAlias + ".*") : (noColumn ? "" : "*"); return StringUtil.concat(mc, joinColumn, ", ", true); } List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_COLUMN); //...;fun0(arg0,arg1,...):fun0;fun1(arg0,arg1,...):fun1;... for (int i = 0; i < keys.length; i++) { 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 提前处理好的 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 提前处理好的 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...\""); } String c = StringUtil.get(keys); c = c + (StringUtil.isEmpty(joinColumn, true) ? "" : ", " + joinColumn);//不能在这里改,后续还要用到: return isMain() && isDistinct() ? PREFIX_DISTINCT + c : c; default: throw new UnsupportedOperationException( "服务器内部错误:getColumnString 不支持 " + RequestMethod.getName(getMethod()) + " 等 [GET,GETS,HEAD,HEADS,POST] 外的ReuqestMethod!" ); } } /**解析@column 中以“;”分隔的表达式("@column":"expression1;expression2;expression2;....")中的expression * @param key * @param expression * @param containRaw * @param allowAlias * @return */ public String parseSQLExpression(String key, String expression, boolean containRaw, boolean allowAlias) { return parseSQLExpression(key, expression, containRaw, allowAlias, null); } /**解析@column 中以“;”分隔的表达式("@column":"expression1;expression2;expression2;....")中的expression * @param key * @param expression * @param containRaw * @param allowAlias * @param 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.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 if (StringUtil.isEmpty(example)) { if (KEY_COLUMN.equals(key)) { example = key + ":\"column0,column1:alias1;function0(arg0,arg1,...);function1(...):alias2...\""; } // 和 key{}:"" 一样 else if (KEY_HAVING.equals(key) || KEY_HAVING_AND.equals(key)) { // exeptionExample = key + ":\"function0(arg0,arg1,...)>1;function1(...)%5<=3...\""; // } else { example = key + ":\"column0!=0;column1+3*2<=10;function0(arg0,arg1,...)>1;function1(...)%5<=3...\""; } } //有函数,但不是窗口函数 int overIndex = expression.indexOf(")OVER("); // 传参不传空格,拼接带空格 ") OVER ("); int againstIndex = expression.indexOf(")AGAINST("); // 传参不传空格,拼接带空格 ") AGAINST ("); boolean containOver = overIndex > 0 && overIndex < expression.length() - ")OVER(".length(); boolean containAgainst = againstIndex > 0 && againstIndex < expression.length() - ")AGAINST(".length(); if (containOver && containAgainst) { throw new IllegalArgumentException("字符 " + expression + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!不能同时存在窗口函数关键词 OVER 和全文索引关键词 AGAINST!"); } if (containOver == false && containAgainst == false) { int end = expression.lastIndexOf(')'); if (start >= end) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" + key + ":value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); } String fun = expression.substring(0, start); if (fun.isEmpty() == false) { if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { if (StringUtil.isName(fun) == false) { throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } } else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); } } String s = expression.substring(start + 1, end); boolean distinct = s.startsWith(PREFIX_DISTINCT); if (distinct) { s = s.substring(PREFIX_DISTINCT.length()); } // 解析函数内的参数 String ckeys[] = parseArgsSplitWithComma(s, false, containRaw, allowAlias); String suffix = expression.substring(end + 1); //:contactCount String alias = null; if (allowAlias) { int index = suffix.lastIndexOf(":"); alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount suffix = index < 0 ? suffix : suffix.substring(0, index); if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { throw new IllegalArgumentException("字符串 " + alias + " 不合法!预编译模式下 " + key + ":value 中 value里面用 ; 分割的每一项" + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); } } if (suffix.isEmpty() == false && (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 ? PREFIX_DISTINCT : "") + StringUtil.get(ckeys) + ")" + suffix; expression = origin + (StringUtil.isEmpty(alias, true) ? "" : gainAs() + quote + alias + quote); } else { //是窗口函数 fun(arg0,agr1) OVER (agr0 agr1 ...) int keyIndex = containOver ? overIndex : againstIndex; String s1 = expression.substring(0, keyIndex + 1); // OVER 前半部分 String s2 = expression.substring(keyIndex + 1); // OVER 后半部分 int index1 = s1.indexOf("("); // 函数 "(" 的起始位置 int end = s2.lastIndexOf(")"); // 后半部分 “)” 的位置 if (index1 >= end + s1.length()) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" + key + ":value 中 value 里的 SQL 函数必须为 function(arg0,arg1,...) 这种格式!"); } String fun = s1.substring(0, index1); // 函数名称 if (fun.isEmpty() == false) { if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { if (StringUtil.isName(fun) == false) { throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } } else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); } } // 获取前半部分函数的参数解析 fun(arg0,agr1) String agrsString1[] = parseArgsSplitWithComma( s1.substring(index1 + 1, s1.lastIndexOf(")")), false, containRaw, allowAlias ); int index2 = s2.indexOf("("); // 后半部分 “(”的起始位置 String argString2 = s2.substring(index2 + 1, end); // 后半部分的参数 // 别名 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 && (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.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 * @return */ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean containRaw, boolean allowAlias) { // 以"," 分割参数 String quote = getQuote(); 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++) { String ck = ckeys[i]; String origin; String alias; // 如果参数包含 "'" ,解析字符串 if (ck.startsWith("`") && ck.endsWith("`")) { origin = ck.substring(1, ck.length() - 1); //sql 注入判断 判断 if (origin.startsWith("_") || StringUtil.isName(origin) == false) { throw new IllegalArgumentException("字符 " + ck + " 不合法!" + "预编译模式下 @column:\"`column0`,`column1`:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中所有字符串 column 都必须必须为1个单词 !"); } origin = gainKey(origin); } else if (ck.startsWith("'") && ck.endsWith("'")) { origin = ck.substring(1, ck.length() - 1); if (origin.contains("'")) { throw new IllegalArgumentException("字符串 " + ck + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' "); } // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 origin = gainValue(origin).toString(); } else { // 参数不包含",",即不是字符串 // 解析参数:1. 字段 ,2. 是以空格分隔的参数 eg: cast(now() as date) if ("=null".equals(ck)) { origin = SQL.isNull(); } else if ("!=null".equals(ck)) { origin = SQL.isNull(false); } else { origin = ck; alias = null; if (allowAlias) { int index = isColumn ? ck.lastIndexOf(":") : -1; //StringUtil.split返回数组中,子项不会有null origin = index < 0 ? ck : ck.substring(0, index); //获取 : 之前的 alias = index < 0 ? null : ck.substring(index + 1); if (isPrepared()) { if (isColumn) { if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { throw new IllegalArgumentException("字符 " + ck + " 不合法!" + "预编译模式下 @column:value 中 value里面用 , 分割的每一项" + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!" + "关键字必须全大写,且以空格分隔的参数,空格必须只有 1 个!其它情况不允许空格!"); } } else { if (origin.startsWith("_") || origin.contains("--")) { // || PATTERN_FUNCTION.matcher(origin).matches() == false) { throw new IllegalArgumentException("字符 " + ck + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !" + "DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); } } } } // 以空格分割参数 String[] mkes = containRaw ? StringUtil.split(ck, " ", true) : new String[]{ ck }; //如果参数中含有空格(少数情况) 比如 fun(arg1, arg2,arg3,arg4) 中的 arg1 arg2 arg3,比如 DISTINCT id if (mkes != null && mkes.length >= 2) { origin = parseArgsSplitWithSpace(mkes); } else { String mk = RAW_MAP.get(origin); if (mk != null) { // newSQLConfig 提前处理好的 if (mk.length() > 0) { origin = mk; } } else if (StringUtil.isNumber(origin)) { //do nothing } else { 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; } } 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.isNotEmpty(alias, true)) { origin += gainAs() + quote + alias + quote; } } } } ckeys[i] = origin; } } return ckeys; } /** * 只解析以空格分隔的参数 * * @param mkes * @return */ private String parseArgsSplitWithSpace(String[] mkes) { String quote = getQuote(); boolean isKeyPrefix = isKeyPrefix(); String tableAlias = quote + gainSQLAlias() + quote; // 包含空格的参数 肯定不包含别名 不用处理别名 if (mkes != null && mkes.length > 0) { for (int j = 0; j < mkes.length; j++) { // now()/AS/ DISTINCT/VALUE 等等放在RAW_MAP中 String origin = mkes[j]; String mk = RAW_MAP.get(origin); if (mk != null) { // newSQLConfig 提前处理好的 if (mk.length() > 0) { mkes[j] = mk; } continue; } //这里为什么还要做一次判断 是因为解析窗口函数调用的时候会判断一次 String ck = origin; // 如果参数包含 "`" 或 "'" ,解析字符串 if (ck.startsWith("`") && ck.endsWith("`")) { origin = ck.substring(1, ck.length() - 1); if (origin.startsWith("_") || StringUtil.isName(origin) == false) { throw new IllegalArgumentException("字符 " + ck + " 不合法!" + "预编译模式下 @column:\"`column0`,`column1`:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中所有字符串 column 都必须必须为1个单词 !"); } mkes[j] = gainKey(origin); continue; } else if (ck.startsWith("'") && ck.endsWith("'")) { origin = ck.substring(1, ck.length() - 1); if (origin.contains("'")) { throw new IllegalArgumentException("字符串 " + ck + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' "); } // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 mkes[j] = gainValue(origin).toString(); continue; } else if (ck.contains("`") || ck.contains("'") || origin.startsWith("_") || origin.contains("--")) { // || PATTERN_FUNCTION.matcher(origin).matches() == false) { throw new IllegalArgumentException("字符 " + origin + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); } if (StringUtil.isNumber(origin)) { //do nothing } else { 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; } } 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; } } // 返回重新以" "拼接后的参数 return StringUtil.join(mkes, " "); } @Override public List> getValues() { return values; } public String getValuesString() { String s = ""; if (values != null && values.size() > 0) { Object[] items = new Object[values.size()]; List vs; for (int i = 0; i < values.size(); i++) { vs = values.get(i); if (vs == null) { continue; } items[i] = "("; for (int j = 0; j < vs.size(); j++) { items[i] += ((j <= 0 ? "" : ",") + gainValue(vs.get(j))); } items[i] += ")"; } s = StringUtil.get(items); } return s; } @Override public AbstractSQLConfig setValues(List> valuess) { this.values = valuess; return this; } @Override public Map getContent() { return content; } @Override public AbstractSQLConfig setContent(Map content) { this.content = content; return this; } @Override public int getCount() { return count; } @Override public AbstractSQLConfig setCount(int count) { this.count = count; return this; } @Override public int getPage() { return page; } @Override public AbstractSQLConfig setPage(int page) { this.page = page; return this; } @Override public int getPosition() { return position; } @Override public AbstractSQLConfig setPosition(int position) { this.position = position; return this; } @Override public int getQuery() { return query; } @Override public AbstractSQLConfig setQuery(int query) { this.query = query; return this; } @Override public Boolean getCompat() { return compat; } @Override public AbstractSQLConfig setCompat(Boolean compat) { this.compat = compat; return this; } @Override public int getType() { return type; } @Override public AbstractSQLConfig setType(int type) { this.type = type; return this; } @Override public int getCache() { return cache; } @Override public AbstractSQLConfig setCache(int cache) { this.cache = cache; return this; } public AbstractSQLConfig setCache(String cache) { return setCache(getCache(cache)); } public static int getCache(String cache) { int cache2; if (cache == null) { cache2 = JSONMap.CACHE_ALL; } else { // if (isSubquery) { // throw new IllegalArgumentException("子查询内不支持传 " + apijson.JSONMap.KEY_CACHE + "!"); // } switch (cache) { case "0": case JSONMap.CACHE_ALL_STRING: cache2 = JSONMap.CACHE_ALL; break; case "1": case JSONMap.CACHE_ROM_STRING: cache2 = JSONMap.CACHE_ROM; break; case "2": case JSONMap.CACHE_RAM_STRING: cache2 = JSONMap.CACHE_RAM; break; default: throw new IllegalArgumentException(JSONMap.KEY_CACHE + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); } } return cache2; } @Override public boolean isExplain() { return explain; } @Override public AbstractSQLConfig setExplain(boolean explain) { this.explain = explain; return this; } @Override public List> getJoinList() { return joinList; } @Override public AbstractSQLConfig setJoinList(List> joinList) { this.joinList = joinList; return this; } @Override public boolean hasJoin() { return joinList != null && joinList.isEmpty() == false; } @Override public boolean isTest() { return test; } @Override public AbstractSQLConfig setTest(boolean test) { this.test = test; return this; } /**获取初始位置offset * @return */ public int getOffset() { return getOffset(getPage(), getCount()); } /**获取初始位置offset * @param page * @param count * @return */ public static int getOffset(int page, int count) { return page*count; } /**获取限制数量 * @return */ 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 ""; } 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 " 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 } @Override public List getNull() { return nulls; } @Override public AbstractSQLConfig setNull(List nulls) { this.nulls = nulls; return this; } @Override public Map getCast() { return cast; } @Override public AbstractSQLConfig setCast(Map cast) { this.cast = cast; return this; } //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< protected int getMaxHavingCount() { return MAX_HAVING_COUNT; } protected int getMaxWhereCount() { return MAX_WHERE_COUNT; } protected int getMaxCombineDepth() { return MAX_COMBINE_DEPTH; } protected int getMaxCombineCount() { return MAX_COMBINE_COUNT; } protected int getMaxCombineKeyCount() { return MAX_COMBINE_KEY_COUNT; } protected float getMaxCombineRatio() { return MAX_COMBINE_RATIO; } @Override public String getCombine() { return combine; } @Override public AbstractSQLConfig setCombine(String combine) { this.combine = combine; return this; } @NotNull @Override public Map> getCombineMap() { List andList = combineMap == null ? null : combineMap.get("&"); if (andList == null) { andList = where == null ? new ArrayList() : new ArrayList(where.keySet()); if (combineMap == null) { combineMap = new HashMap<>(); } combineMap.put("&", andList); } return combineMap; } @Override public AbstractSQLConfig setCombineMap(Map> combineMap) { this.combineMap = combineMap; return this; } @Override public Map getWhere() { return where; } @Override public AbstractSQLConfig setWhere(Map where) { this.where = where; return this; } /** * noFunctionChar = false * @param key * @return */ @Override public Object getWhere(String key) { return getWhere(key, false); } //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 /** * @param key - the key passed in * @param exactMatch - whether it is exact match * @return *

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

*/ @Override public Object getWhere(String key, boolean exactMatch) { if (exactMatch) { return where == null ? null : where.get(key); } if (key == null || where == null){ return null; } int index; for (Entry entry : where.entrySet()) { String k = entry.getKey(); index = k.indexOf(key); if (index >= 0 && StringUtil.isName(k.substring(index, index + 1)) == false) { return entry.getValue(); } } return null; } @Override public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { if (key != null) { if (where == null) { where = new LinkedHashMap<>(); } if (value == null) { where.remove(key); } else { where.put(key, value); } Map> combineMap = getCombineMap(); List andList = combineMap.get("&"); if (value == null) { if (andList != null) { andList.remove(key); } } else if (andList == null || andList.contains(key) == false) { int i = 0; if (andList == null) { andList = new ArrayList<>(); } else if (prior && andList.isEmpty() == false) { String idKey = getIdKey(); String idInKey = idKey + "{}"; String userIdKey = getUserIdKey(); String userIdInKey = userIdKey + "{}"; int lastIndex; if (key.equals(idKey)) { setId(value); lastIndex = -1; } else if (key.equals(idInKey)) { setIdIn(value); lastIndex = andList.lastIndexOf(idKey); } else if (key.equals(userIdKey)) { setUserId(value); lastIndex = andList.lastIndexOf(idInKey); if (lastIndex < 0) { lastIndex = andList.lastIndexOf(idKey); } } else if (key.equals(userIdInKey)) { setUserIdIn(value); lastIndex = andList.lastIndexOf(userIdKey); if (lastIndex < 0) { lastIndex = andList.lastIndexOf(idInKey); } if (lastIndex < 0) { lastIndex = andList.lastIndexOf(idKey); } } else { lastIndex = andList.lastIndexOf(userIdInKey); if (lastIndex < 0) { lastIndex = andList.lastIndexOf(userIdKey); } if (lastIndex < 0) { lastIndex = andList.lastIndexOf(idInKey); } if (lastIndex < 0) { lastIndex = andList.lastIndexOf(idKey); } } i = lastIndex + 1; } if (prior) { andList.add(i, key); // userId 的优先级不能比 id 高 0, key); } else { // AbstractSQLExecutor.onPutColumn 里 getSQL,要保证缓存的 SQL 和查询的 SQL 里 where 的 key:value 顺序一致 andList.add(key); } } combineMap.put("&", andList); } return this; } /**获取WHERE * @return * @throws Exception */ @Override public String gainWhereString(boolean hasPrefix) throws Exception { String combineExpr = getCombine(); if (StringUtil.isEmpty(combineExpr, false)) { return getWhereString(hasPrefix, getMethod(), getWhere(), getCombineMap(), getJoinList(), ! isTest()); } return getWhereString(hasPrefix, getMethod(), getWhere(), combineExpr, getJoinList(), ! isTest()); } /**获取WHERE * @param method * @param where * @return * @throws Exception */ 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; if (result.isEmpty() && RequestMethod.isQueryMethod(method) == false) { throw new UnsupportedOperationException("写操作请求必须带条件!!!"); } return result; } /**解析 @combine 条件 key 组合的与或非+括号的逻辑运算表达式为具体的完整条件组合 * @param method * @param quote * @param table * @param alias * @param conditionMap where 或 having 对应条件的 Map * @param combine * @param verifyName * @param containRaw * @param isHaving * @return * @throws Exception */ protected String parseCombineExpression(RequestMethod method, String quote, String table, String alias , Map conditionMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception { String errPrefix = table + (isHaving ? ":{ @having:{ " : ":{ ") + "@combine:'" + combine + (isHaving ? "' } }" : "' }"); String s = StringUtil.get(combine); if (s.startsWith(" ") || s.endsWith(" ") ) { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!不允许首尾有空格,也不允许连续空格!空格不能多也不能少!" + "逻辑连接符 & | 左右必须各一个相邻空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); } if (conditionMap == null) { conditionMap = new HashMap<>(); } int size = conditionMap.size(); int maxCount = isHaving ? getMaxHavingCount() : getMaxWhereCount(); if (maxCount > 0 && size > maxCount) { throw new IllegalArgumentException(table + (isHaving ? ":{ @having:{ " : ":{ ") + "key0:value0, key1:value1... " + combine + (isHaving ? " } }" : " }") + " 中条件 key:value 数量 " + size + " 已超过最大数量,必须在 0-" + maxCount + " 内!"); } String result = ""; List 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) { // 只收集表达式条件值 setPreparedValueList(new ArrayList<>()); // 必须反过来,否则 JOIN ON 内部 @combine 拼接后顺序错误 } int maxDepth = getMaxCombineDepth(); int maxCombineCount = getMaxCombineCount(); int maxCombineKeyCount = getMaxCombineKeyCount(); float maxCombineRatio = getMaxCombineRatio(); int depth = 0; int allCount = 0; int i = 0; char lastLogic = 0; char last = 0; boolean first = true; boolean isNot = false; String key = ""; while (i <= n) { // "date> | (contactIdList<> & (name*~ | tag&$))" boolean isOver = i >= n; char c = isOver ? 0 : s.charAt(i); boolean isBlankOrRightParenthesis = c == ' ' || c == ')'; if (isOver || isBlankOrRightParenthesis) { boolean isEmpty = StringUtil.isEmpty(key, true); if (isEmpty && last != ')') { throw new IllegalArgumentException(errPrefix + " 中字符 '" + (isOver ? s : s.substring(i)) + "' 不合法!" + (c == ' ' ? "空格 ' ' " : "右括号 ')'") + " 左边缺少条件 key !逻辑连接符 & | 左右必须各一个相邻空格!" + "空格不能多也不能少!不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); } if (isEmpty == false) { if (first == false && lastLogic <= 0) { throw new IllegalArgumentException(errPrefix + " 中字符 " + "'" + s.substring(i - key.length() - (isOver ? 1 : 0)) + "' 不合法!左边缺少 & | 其中一个逻辑连接符!"); } allCount ++; if (allCount > maxCombineCount && maxCombineCount > 0) { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!" + "其中 key 数量 " + allCount + " 已超过最大值,必须在条件键值对数量 0-" + maxCombineCount + " 内!"); } 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 + " 倍内!"); } if (StringUtil.isEmpty(wi, true)) { // 转成 1=1 ? throw new IllegalArgumentException(errPrefix + " 中字符 '" + key + "' 对应的 " + column + ":value 不是有效条件键值对!"); } Integer count = usedKeyCountMap.get(column); count = count == null ? 1 : count + 1; if (count > maxCombineKeyCount && maxCombineKeyCount > 0) { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!" + "其中 '" + column + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); } usedKeyCountMap.put(column, count); result += "( " + gainCondition(isNot, wi) + " )"; isNot = false; first = false; } key = ""; lastLogic = 0; if (isOver) { break; } } if (c == ' ') { } else if (c == '&') { if (last == ' ') { if (i >= n - 1 || s.charAt(i + 1) != ' ') { throw new IllegalArgumentException(errPrefix + " 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1)) + "' 不合法!逻辑连接符 & 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); } result += SQL.AND; lastLogic = c; i ++; } else { key += c; } } else if (c == '|') { if (last == ' ') { if (i >= n - 1 || s.charAt(i + 1) != ' ') { throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1)) + "' 不合法!逻辑连接符 | 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + "不允许首尾有空格,也不允许连续空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); } result += SQL.OR; lastLogic = c; i ++; } else { key += c; } } else if (c == '!') { last = i <= 0 ? 0 : s.charAt(i - 1); // & | 后面跳过了空格 char next = i >= n - 1 ? 0 : s.charAt(i + 1); if (last == ' ' || last == '(') { if (next == ' ') { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + "' 不合法!非逻辑符 '!' 右边多了一个空格 ' ' !非逻辑符 '!' " + "右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); } if (next == ')' || next == '&' || next == '!') { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + "' 不合法!非逻辑符 '!' 右边多了一个字符 '" + next + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); } if (i > 0 && lastLogic <= 0 && last != '(') { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(i) + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); } } if (next == '(') { result += SQL.NOT; lastLogic = c; } else if (last <= 0 || last == ' ' || last == '(') { isNot = true; // lastLogic = c; } else { key += c; } } else if (c == '(') { if (key.isEmpty() == false || (i > 0 && lastLogic <= 0 && last != '(')) { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(i) + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); } depth ++; if (depth > maxDepth && maxDepth > 0) { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!"); } result += c; lastLogic = 0; first = true; } else if (c == ')') { depth --; if (depth < 0) { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + "' 不合法!左括号 ( 比 右括号 ) 少!数量必须相等从而完整闭合 (...) !"); } result += c; lastLogic = 0; } else { key += c; } last = c; i ++; } if (depth != 0) { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!左括号 ( 比 右括号 ) 多!数量必须相等从而完整闭合 (...) !"); } } List exprPreparedValues = getPreparedValueList(); if (isHaving == false) { // 只收集 AND 条件值 setPreparedValueList(new ArrayList<>()); } Set> set = conditionMap.entrySet(); String andCond = ""; boolean isItemFirst = true; for (Entry entry : set) { String key = entry == null ? null : entry.getKey(); if (key == null || usedKeyCountMap.containsKey(key)) { continue; } String wi = isHaving ? gainHavingItem(quote, table, alias, key, (String) entry.getValue(), containRaw) : gainWhereItem(key, entry.getValue(), method, verifyName); if (StringUtil.isEmpty(wi, true)) {//避免SQL条件连接错误 continue; } andCond += (isItemFirst ? "" : AND) + "(" + wi + ")"; isItemFirst = false; } if (isHaving == false) { // 优先存放 AND 条件值 preparedValues.addAll(getPreparedValueList()); } if (StringUtil.isEmpty(result, true)) { result = andCond; } else if (StringUtil.isNotEmpty(andCond, true)) { // andCond 必须放后面,否则 prepared 值顺序错误 if (isHaving) { // HAVING 前 WHERE 已经有条件 ? 占位,不能反过来,想优化 AND 连接在最前,需要多遍历一次内部的 key,也可以 newSQLConfig 时存到 andList result = "( " + result + " )" + AND + andCond; } else { result = andCond + AND + "( " + result + " )"; // 先暂存之前的 prepared 值,然后反向整合 } } if (isHaving == false) { if (exprPreparedValues != null && exprPreparedValues.isEmpty() == false) { preparedValues.addAll(exprPreparedValues); // 在 AND 条件值后存放表达式内的条件值 } setPreparedValueList(preparedValues); } return result; } /**@combine:"a,b" 条件组合。虽然有了 @combine:"a | b" 这种新方式,但为了 Join 多个 On 能保证顺序正确,以及这个性能更好,还是保留这个方式 * @param hasPrefix * @param method * @param where * @param combine * @param joinList * @param verifyName * @return * @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 \"\";"); return ""; } List keyList; String whereString = ""; boolean isCombineFirst = true; int logic; boolean isItemFirst; String c; String cs; for (Entry> ce : combineSet) { keyList = ce == null ? null : ce.getValue(); if (keyList == null || keyList.isEmpty()) { continue; } if ("|".equals(ce.getKey())) { logic = Logic.TYPE_OR; } else if ("!".equals(ce.getKey())) { logic = Logic.TYPE_NOT; } else { logic = Logic.TYPE_AND; } isItemFirst = true; cs = ""; for (String key : keyList) { c = gainWhereItem(key, where.get(key), method, verifyName); if (StringUtil.isEmpty(c, true)) {//避免SQL条件连接错误 continue; } cs += (isItemFirst ? "" : (Logic.isAnd(logic) ? AND : OR)) + "(" + c + ")"; isItemFirst = false; } if (StringUtil.isEmpty(cs, true)) {//避免SQL条件连接错误 continue; } whereString += (isCombineFirst ? "" : AND) + (Logic.isNot(logic) ? NOT : "") + " ( " + cs + " ) "; isCombineFirst = false; } whereString = concatJoinWhereString(whereString); String s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; if (s.isEmpty() && RequestMethod.isQueryMethod(method) == false) { throw new UnsupportedOperationException("写操作请求必须带条件!!!"); } return s; } protected String concatJoinWhereString(String whereString) throws Exception { List> joinList = getJoinList(); if (joinList != null) { String newWs = ""; String ws = whereString; List newPvl = new ArrayList<>(); List pvl = new ArrayList<>(getPreparedValueList()); SQLConfig jc; SQLConfig outerConfig; String js; boolean isWsEmpty = StringUtil.isEmpty(ws, true); boolean changed = false; // 各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? for (Join j : joinList) { String jt = j.getJoinType(); switch (jt) { case "*": // CROSS JOIN case "@": // APP JOIN case "<": // LEFT JOIN case ">": // RIGHT JOIN 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 "!": // 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.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); if (isWsEmpty) { if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); } if (isForeignJoin) { // ) FOREIGN JOIN: B & ! A throw new NotExistException("no result for ) FOREIGN JOIN( B & ! A ) when A is empty!"); } } if (StringUtil.isEmpty(js, true)) { if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); } if (isAntiJoin) { // ( ANTI JOIN: A & ! B throw new NotExistException("no result for ( ANTI JOIN( A & ! B ) when B is empty!"); } if (isWsEmpty) { if (isSideJoin) { throw new NotExistException("no result for ^ SIDE JOIN( ! (A & B) ) when both A and B are empty!"); } } else { if (isSideJoin || isForeignJoin) { newWs += " ( " + gainCondition(true, ws) + " ) "; newPvl.addAll(pvl); newPvl.addAll(jc.getPreparedValueList()); changed = true; } } continue; } if (StringUtil.isNotEmpty(newWs, true)) { newWs += AND; } if (isAntiJoin) { // ( ANTI JOIN: A & ! B newWs += " ( " + ( isWsEmpty ? "" : ws + AND ) + NOT + " ( " + js + " ) " + " ) "; } else if (isForeignJoin) { // ) FOREIGN JOIN: (! A) & B // preparedValueList.add 不好反过来 B & ! A newWs += " ( " + NOT + " ( " + ws + " ) ) " + AND + " ( " + js + " ) "; } else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) //MySQL 因为 NULL 值处理问题,(A & ! B) | (B & ! A) 与 ! (A & B) 返回结果不一样,后者往往更多 newWs += " ( " + 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 += " ( " + gainCondition( Logic.isNot(logic), ws + ( isWsEmpty ? "" : (Logic.isAnd(logic) ? AND : OR) ) + " ( " + js + " ) " ) + " ) "; } newPvl.addAll(pvl); newPvl.addAll(jc.getPreparedValueList()); changed = true; break; default: throw new UnsupportedOperationException( "join:value 中 value 里的 " + jt + "/" + j.getPath() + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN, ~ ASOF ] 之外的 JOIN 类型 !" ); } } if (changed) { whereString = newWs; setPreparedValueList(newPvl); } } return whereString; } /** * @param key * @param value * @param method * @param verifyName * @return * @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); if (key == null || key.endsWith("()") || key.startsWith("@")) { //关键字||方法, +或-直接报错 Log.d(TAG, "getWhereItem key == null || key.endsWith(()) || key.startsWith(@) >> continue;"); return null; } 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 } else if (key.endsWith("%")) { keyType = 3; } else if (key.endsWith("{}")) { keyType = 4; } else if (key.endsWith("}{")) { keyType = 5; } else if (key.endsWith("<>")) { keyType = 6; } else if (key.endsWith(">=")) { keyType = 7; } else if (key.endsWith("<=")) { keyType = 8; } else if (key.endsWith(">")) { keyType = 9; } else if (key.endsWith("<")) { keyType = 10; } else { // else绝对不能省,避免再次踩坑! keyType = 0; 写在for循环外面都没注意! keyType = 0; } String column = gainRealKey(method, key, false, true, verifyName); // 原始 SQL 片段 String rawSQL = gainRawSQL(key, value); switch (keyType) { case 1: return gainSearchString(key, column, value, rawSQL); case -2: case 2: return gainRegExpString(key, column, value, keyType < 0, rawSQL); case 3: return gainBetweenString(key, column, value, rawSQL); case 4: return gainRangeString(key, column, value, rawSQL); case 5: return gainExistsString(key, column, value, rawSQL); case 6: return gainContainString(key, column, value, rawSQL); case 7: return gainCompareString(key, column, value, ">=", rawSQL); case 8: return gainCompareString(key, column, value, "<=", rawSQL); case 9: return gainCompareString(key, column, value, ">", rawSQL); case 10: return gainCompareString(key, column, value, "<", rawSQL); default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! return gainEqualString(key, column, value, rawSQL); } } 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("!"); // & | 没有任何意义,写法多了不好控制 if (not) { column = column.substring(0, column.length() - 1); } 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 gainKey(column) + logic + (value instanceof Subquery ? gainSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : gainValue(key, column, value))); } 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] 内的类型 !"); } String rc = column.endsWith("[") || column.endsWith("{") ? column.substring(0, column.length() - 1) : column; if ( ! StringUtil.isName(rc)) { throw new IllegalArgumentException(key + ":value 中 key 不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); } return gainKey(column) + " " + type + " " + (value instanceof Subquery ? gainSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : gainValue(key, column, value))); } 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 gainSQLValue(key).toString(); } 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 gainSQLKey(String key) { String q = getQuote(); return (isKeyPrefix() ? q + gainSQLAlias() + q + "." : "") + q + key + q; } /** * 使用prepareStatement预编译,值为 ? ,后续动态set进去 */ protected Object gainValue(@NotNull Object value) { return gainValue(null, null, value); } protected List preparedValueList = new ArrayList<>(); protected Object gainValue(String key, String column, Object value) { if (isPrepared()) { if (value == null) { return null; } Map castMap = getCast(); String type = key == null || castMap == null ? null : castMap.get(key); // if ("DATE".equalsIgnoreCase(type) && value instanceof Date == false) { // value = value instanceof Number ? new Date(((Number) value).longValue()) : Date.valueOf((String) value); // } // else if ("TIME".equalsIgnoreCase(type) && value instanceof Time == false) { // value = value instanceof Number ? new Time(((Number) value).longValue()) : Time.valueOf((String) value); // } // else if ("TIMESTAMP".equalsIgnoreCase(type) && value instanceof Timestamp == false) { // value = value instanceof Number ? new Timestamp(((Number) value).longValue()) : Timestamp.valueOf((String) value); // } // else if ("ARRAY".equalsIgnoreCase(type) && value instanceof Array == false) { // value = ((Collection) value).toArray(); // } // else if (StringUtil.isEmpty(type, true) == false) { // preparedValueList.add(value); // return "cast(?" + SQL.AS + type + ")"; // } preparedValueList.add(value); return StringUtil.isEmpty(type, true) ? "?" : "cast(?" + SQL.AS + type + ")"; } return key == null ? gainSQLValue(value) : gainSQLValue(key, column, 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 = gainSQLValue(value); return StringUtil.isEmpty(type, true) ? val : "cast(" + val + SQL.AS + type + ")"; } 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 隐式转换用不了索引 } @Override public List getPreparedValueList() { return preparedValueList; } @Override public AbstractSQLConfig setPreparedValueList(List preparedValueList) { this.preparedValueList = preparedValueList; return this; } //$ search <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**search key match value * @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 !"); } if (value == null) { return ""; } Logic logic = new Logic(column); column = logic.getKey(); Log.i(TAG, "getSearchString column = " + column); List arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } return gainSearchString(key, column, arr.toArray(), logic.getType()); } /**search key match values * @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 ""; } String condition = ""; for (int i = 0; i < values.length; i++) { Object v = values[i]; if (v instanceof String == false) { throw new IllegalArgumentException(key + ":value 中 value 的类型只能为 String 或 String[]!"); } if (((String) v).isEmpty()) { // 允许查空格 StringUtil.isEmpty((String) v, true) throw new IllegalArgumentException(key + ":value 中 value 值 " + v + "是空字符串,没有意义,不允许这样传!"); } // if (((String) v).contains("%%")) { // 需要通过 %\%% 来模糊搜索 % // throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); // } condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + gainLikeString(key, column, (String) v); } return gainCondition(Logic.isNot(type), condition); } /**WHERE key LIKE 'value' * @param key * @param column * @param value * @return key LIKE '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); char l; if (r == '%' || r == '_' || r == '?') { k = k.substring(0, k.length() - 1); l = k.charAt(k.length() - 1); if (l == '%' || l == '_' || l == '?') { if (l == r) { throw new IllegalArgumentException(key + ":value 中字符 " + k + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); } k = k.substring(0, k.length() - 1); } else if (l > 0 && StringUtil.isName(String.valueOf(l))) { l = r; } if (l == '?') { l = 0; } if (r == '?') { r = 0; } } else { l = r = 0; } if (l > 0 || r > 0) { if (value == null) { throw new IllegalArgumentException(key + ":value 中 value 为 null!" + "key$:value 中 value 不能为 null,且类型必须是 String !"); } value = value.replaceAll("\\\\", "\\\\\\\\"); value = value.replaceAll("\\%", "\\\\%"); value = value.replaceAll("\\_", "\\\\_"); if (l > 0) { value = l + value; } if (r > 0) { value = value + r; } } return gainKey(column) + " LIKE " + gainValue(key, column, value); } //$ search >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //~ regexp <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**search key match RegExp values * @param key * @param column * @param value * @param ignoreCase * @return {@link #gainRegExpString(String, String, Object[], int, boolean)} * @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 !"); } if (value == null) { return ""; } Logic logic = new Logic(column); column = logic.getKey(); Log.i(TAG, "getRegExpString column = " + column); L arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } return gainRegExpString(key, column, arr.toArray(), logic.getType(), ignoreCase); } /**search key match RegExp values * @param key * @param values * @param type * @param ignoreCase * @return LOGIC [ key REGEXP 'values[i]' ] * @throws IllegalArgumentException */ public String gainRegExpString(String key, String column, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; } String condition = ""; for (int i = 0; i < values.length; i++) { if (values[i] instanceof String == false) { throw new IllegalArgumentException(key + ":value 中value的类型只能为String或String[]!"); } condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + gainRegExpString(key, column, (String) values[i], ignoreCase); } return gainCondition(Logic.isNot(type), condition); } /**WHERE key REGEXP 'value' * @param key * @param value * @param ignoreCase * @return key REGEXP '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 (isPresto() || isTrino()) { return "regexp_like(" + (ignoreCase ? "lower(" : "") + gainKey(column) + (ignoreCase ? ")" : "") + ", " + (ignoreCase ? "lower(" : "") + gainValue(key, column, value) + (ignoreCase ? ")" : "") + ")"; } if (isClickHouse()) { 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(" : "") + gainKey(column) + (ignoreCase ? ")" : "") + " REGEXP " + (ignoreCase ? "lower(" : "") + gainValue(key, column, value) + (ignoreCase ? ")" : ""); } return gainKey(column) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + gainValue(key, column, value); } //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //% between <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**WHERE key BETWEEN 'start' AND 'end' * @param key * @param value 'start,end' * @return LOGIC [ key BETWEEN 'start' AND 'end' ] * @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 !"); } if (value == null) { return ""; } Logic logic = new Logic(column); column = logic.getKey(); Log.i(TAG, "getBetweenString column = " + column); L arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } return gainBetweenString(key, column, arr.toArray(), logic.getType()); } /**WHERE key BETWEEN 'start' AND 'end' * @param key * @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 */ public String gainBetweenString(String key, String column, Object[] values, int type) throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; } String condition = ""; String[] vs; for (int i = 0; i < values.length; i++) { if (values[i] instanceof String == false) { throw new IllegalArgumentException(key + ":value 中 value 的类型只能为 String 或 String[] !"); } vs = StringUtil.split((String) values[i]); if (vs == null || vs.length != 2) { throw new IllegalArgumentException(key + ":value 中 value 不合法!类型为 String 时必须包括1个逗号 , " + "且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); } condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + gainBetweenString(key, column, vs[0], (Object) vs[1]) + ")"; } return gainCondition(Logic.isNot(type), condition); } /**WHERE key BETWEEN 'start' AND 'end' * @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 gainKey(column) + " BETWEEN " + gainValue(key, column, start) + AND + gainValue(key, column, end); } //% between >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //{} range <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**WHERE key > 'key0' AND key <= 'key1' AND ... * @param key * @param range "condition0,condition1..." * @return key condition0 AND key condition1 AND ... * @throws Exception */ 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"); } Logic logic = new Logic(column); String k = logic.getKey(); Log.i(TAG, "getRangeString k = " + k); if (range instanceof List) { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + "Raw SQL 不支持 key{}:[] 这种键值对!"); } if (logic.isOr() || logic.isNot()) { List l = (List) range; if (logic.isNot() && l.isEmpty()) { return ""; // key!{}: [] 这个条件无效,加到 SQL 语句中 key IN() 会报错,getInString 里不好处理 } return gainKey(k) + gainInString(k, column, l.toArray(), logic.isNot()); } throw new IllegalArgumentException(key + ":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); } else if (range instanceof String) {//非Number类型需要客户端拼接成 < 'value0', >= 'value1'这种 String condition = ""; String[] cs = rawSQL != null ? null : StringUtil.split((String) range, ";", false); if (rawSQL != null) { int index = rawSQL.indexOf("("); 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...\""); } else { 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)) { c = SQL.isNull(); } else if ("!=null".equals(c)) { c = SQL.isNull(false); } else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() == false)) { throw new UnsupportedOperationException(key + ":value 的 value 中 " + c + " 不合法!" + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null " + "或 符合正则表达式 " + PATTERN_RANGE + " !不允许连续减号 -- !不允许空格!"); } expr += (j <= 0 ? "" : lk) + fk + c; } } condition += ((i <= 0 ? "" : lk) + expr); } } if (condition.isEmpty()) { return ""; } return gainCondition(logic.isNot(), condition); } 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() + "!range 只能是 用','分隔条件的字符串 或者 可取选项JSONArray!"); } /**WHERE key IN ('key0', 'key1', ... ) * @param in * @return IN ('key0', 'key1', ... ) * @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 ? "," : "") + gainValue(key, column, in[i])); } } if (condition.isEmpty()) {//条件如果存在必须执行,不能忽略。条件为空会导致出错,又很难保证条件不为空(@:条件),所以还是这样好 throw new NotExistException(TAG + ".getInString(" + key + "," + column + ", [], " + not + ") >> condition.isEmpty() >> IN()"); } return (not ? NOT : "") + " IN (" + condition + ")"; } //{} range >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //}{ exists <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**WHERE EXISTS subquery * 如果合并到 getRangeString,一方面支持不了 [1,2,2] 和 ">1" (转成 EXISTS(SELECT IN ) 需要 * static newSQLConfig,但它不能传入子类实例,除非不是 static),另一方面多了子查询临时表性能会比 IN 差 * @param key * @param value * @return EXISTS ALL(SELECT ...) * @throws NotExistException */ 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 !"); } if (value == null) { return ""; } if (value instanceof Subquery == false) { throw new IllegalArgumentException(key + ":subquery 类型为" + value.getClass().getSimpleName() + "!subquery 只能是 子查询JSONObejct!"); } Logic logic = new Logic(column); column = logic.getKey(); Log.i(TAG, "getExistsString column = " + column); return (logic.isNot() ? NOT : "") + " EXISTS " + gainSubqueryString((Subquery) value); } //}{ exists >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //<> contain <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**WHERE key contains value * @param key * @param value * @return {@link #gainContainString(String, String, Object[], int)} * @throws NotExistException */ 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 !"); } Logic logic = new Logic(column); column = logic.getKey(); Log.i(TAG, "getContainString column = " + column); 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 其它功能等冲突 * @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 */ 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 类型不能为 [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 !"); } c = ((Map) c).get("value"); if (c instanceof Collection || c instanceof Map) { throw new IllegalArgumentException(key + ":{ path:path, value:value } 中 value 类型" + "不能为 [JSONMap, JSONList, Collection, Map] 中的任何一个 !"); } } condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); if (isPSQL()) { condition += (gainKey(column) + " @> " + gainValue(key, column, newJSONArray(c))); // operator does not exist: jsonb @> character varying "[" + c + "]"); } 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(" + gainKey(column) + "))" + ", " + gainValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + gainValue(key, column, path)) + ")"); } else { condition += ("json_contains(" + gainKey(column) + ", " + gainValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + gainValue(key, column, path)) + ")"); } } } if (condition.isEmpty()) { condition = gainKey(column) + SQL.isNull(true) + OR + gainLikeString(key, column, "[]"); // key = '[]' 无结果! } else { condition = gainKey(column) + SQL.isNull(false) + AND + "(" + condition + ")"; } } if (condition.isEmpty()) { return ""; } return gainCondition(not, condition); } //<> contain >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //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 gainSubqueryString(Subquery subquery) throws Exception { if (subquery == null) { return ""; } 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 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); } setPreparedValueList(pvl); } return sql; } //key@:{} Subquery >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /**拼接条件 * @param not * @param condition * @return */ public static String gainCondition(boolean not, String condition) { return gainCondition(not, condition, false); } /**拼接条件 * @param not * @param condition * @param addOuterBracket * @return */ public static String gainCondition(boolean not, String condition, boolean addOuterBracket) { String s = not ? NOT + "(" + condition + ")" : condition; return addOuterBracket ? "( " + s + " )" : s; } /**转为JSONArray * @param obj * @return */ @NotNull public static > L newJSONArray(Object obj) { L array = JSON.createJSONArray(); if (obj != null) { if (obj instanceof Collection) { array.addAll((Collection) obj); } else { array.add(obj); } } return array; } //WHERE >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //SET <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**获取SET * @return * @throws Exception */ public String gainSetString() throws Exception { return gainSetString(getMethod(), getContent(), ! isTest()); } //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 /**获取SET * @param method -the method used * @param content -the content map * @return * @throws Exception *

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

*/ public String gainSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { Set set = content == null ? null : content.keySet(); String setString = ""; if (set != null && set.size() > 0) { boolean isFirst = true; int keyType;// 0 - =; 1 - +, 2 - - Object value; String idKey = getIdKey(); for (Entry entry : content.entrySet()) { String key = entry.getKey(); //避免筛选到全部 value = key == null ? null : content.get(key); if (key == null || idKey.equals(key)) { continue; } if (key.endsWith("+")) { keyType = 1; } else if (key.endsWith("-")) { keyType = 2; } else { keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 } value = entry.getValue(); String column = gainRealKey(method, key, false, true, verifyName); setString += (isFirst ? "" : ", ") + (gainKey(column) + " = " + (keyType == 1 ? gainAddString(key, column, value) : (keyType == 2 ? gainRemoveString(key, column, value) : gainValue(key, column, value)) ) ); isFirst = false; } } if (setString.isEmpty()) { throw new IllegalArgumentException("PUT 请求必须在Table内设置要修改的 key:value !"); } return (isClickHouse() ? " " : " SET ") + setString; } /**SET key = concat(key, 'value') * @param key * @param value * @return concat(key, 'value') * @throws IllegalArgumentException */ public String gainAddString(String key, String column, Object value) throws IllegalArgumentException { if (value instanceof Number) { return gainKey(column) + " + " + value; } if (value instanceof String) { return SQL.concat(gainKey(column), (String) gainValue(key, column, value)); } throw new IllegalArgumentException(key + ":value 中 value 类型错误,必须是 Number,String,Array 中的任何一种!"); } /**SET key = replace(key, 'value', '') * @param key * @param value * @return REPLACE (key, 'value', '') * @throws IllegalArgumentException */ public String gainRemoveString(String key, String column, Object value) throws IllegalArgumentException { if (value instanceof Number) { return gainKey(column) + " - " + value; } if (value instanceof String) { 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 */ @Override 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 */ 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; ... // for (...) { Call procedure1();\n SQL \n; Call procedure2(); ... } // 貌似不需要,因为 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 q = config.getQuote(); return "CALL " + q + sch + q + "." + (hasPrefix ? procedure.substring(ind + 1) : procedure); } String tablePath = config.gainTablePath(); if (StringUtil.isEmpty(tablePath, true)) { Log.i(TAG, "getSQL StringUtil.isEmpty(tablePath, true) >> return null;"); return null; } // 解决重复添加导致报错: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(); } String sql = "SELECT " + (config.getCache() == JSONMap.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + gainConditionString(tablePath, config); return explain + config.gainOraclePageSQL(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"; } cSql = withAsExpreSql + cSql; config.clearWithAsExprListIfNeed(); } return cSql; } @Override public boolean isWithAsEnable() { return ENABLE_WITH_AS && (isMySQL() == false || gainDBVersionNums()[0] >= 8); } /**Oracle的分页获取 * @param sql * @return */ 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 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 table * @param config * @return * @throws Exception */ private static , L extends List> String gainConditionString( String table, AbstractSQLConfig config) throws Exception { Subquery from = config.getFrom(); if (from != null) { 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.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.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.gainHavingString(true) ; } else { aggregation = ""; } String condition = table + join + where + aggregation; ; //+ config.getLimitString(); //no need to optimize // if (config.getPage() <= 0 || ID.equals(column.trim())) { return condition; // config.isOracle() ? condition : condition + config.getLimitString(); // } // // // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex <<<<<<<<<<<<<<<<<<< // String order = StringUtil.getNoBlankString(config.getOrder()); // List orderList = order.isEmpty() ? null : Arrays.asList(StringUtil.split(order)); // // int type = 0; // if (BaseModel.isEmpty(orderList) || BaseModel.isContain(orderList, ID+"+")) { // type = 1; // } // else if (BaseModel.isContain(orderList, ID+"-")) { // type = 2; // } // // if (type > 0) { // return condition.replace("WHERE", // "WHERE id " + (type == 1 ? ">=" : "<=") + " (SELECT id FROM " + table // + where + " ORDER BY id " + (type == 1 ? "ASC" : "DESC") + " LIMIT " + config.getOffset() + ", 1) AND" // ) // + " LIMIT " + config.getCount(); //子查询起始id不一定准确,只能作为最小可能! ;// // } // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex >>>>>>>>>>>>>>>>>> // // // //结果错误!SELECT * FROM User AS t0 INNER JOIN // (SELECT id FROM User ORDER BY date ASC LIMIT 20, 10) AS t1 ON t0.id = t1.id // //common case, inner join // condition += config.getLimitString(); // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; } private boolean keyPrefix; @Override public boolean isKeyPrefix() { return keyPrefix; } @Override public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { this.keyPrefix = keyPrefix; return this; } public String gainJoinString() throws Exception { String joinOns = ""; if (joinList != null) { String quote = getQuote(); List pvl = getPreparedValueList(); // new ArrayList<>(); //boolean changed = false; // 主表不用别名 String ta; for (Join j : joinList) { onGainJoinString(j); if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) continue; } String type = j.getJoinType(); //LEFT JOIN sys.apijson_user AS User ON User.id = Moment.userId, 都是用 = ,通过relateType处理缓存 // <"INNER JOIN User ON User.id = Moment.userId", UserConfig> TODO AS 放 getSQLTable 内 SQLConfig jc = j.getJoinConfig(); jc.setPrepared(isPrepared()); // 将关联表所属数据源配置为主表数据源 jc.setDatasource(this.getDatasource()); String jt = jc.gainSQLAlias(); List onList = j.getOnList(); //如果要强制小写,则可在子类重写这个方法再 toLowerCase // if (DATABASE_POSTGRESQL.equals(getDatabase())) { // jt = jt.toLowerCase(); // tn = tn.toLowerCase(); // } String sql; switch (type) { //前面已跳过 case "@": // APP JOIN // continue; case "*": // CROSS JOIN onGainCrossJoinString(j); case "<": // LEFT JOIN case ">": // RIGHT JOIN jc.setMain(true).setKeyPrefix(false); sql = ( "<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS") ) + " 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; break; case "&": // INNER JOIN: A & B case "": // FULL JOIN: A | B case "|": // FULL JOIN: A | B case "!": // OUTER JOIN: ! (A | B) case "^": // SIDE JOIN: ! (A & B) case "(": // ANTI JOIN: A & ! B case ")": // FOREIGN JOIN: B & ! A sql = " INNER JOIN " + jc.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 里的 " + k + "/" + j.getPath() + "错误!不支持 " + k + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN, ~ ASOF ] 之外的 JOIN 类型 !" ); } SQLConfig oc = j.getOnConfig(); String ow = null; if (oc != null) { oc.setPrepared(isPrepared()); oc.setPreparedValueList(new ArrayList<>()); oc.setMain(false).setKeyPrefix(true); ow = oc.gainWhereString(false); pvl.addAll(oc.getPreparedValueList()); //changed = true; } joinOns += " \n " + sql + (StringUtil.isEmpty(ow, true) ? "" : " AND ( " + ow + " ) "); } //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 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, 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) + lk + (isNot ? " != " : " = ") + rk; } else { 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 + "/" + join.getPath() + " 中 JOIN ON 条件关联逻辑符 " + rt + " 不合法! >, <, >=, <= 不支持与或非逻辑符 & | ! !"); } sql += (first ? ON : AND) + lk + " " + rt + " " + rk; } else if (rt.endsWith("$")) { String t = rt.substring(0, rt.length() - 1); char r = t.isEmpty() ? 0 : t.charAt(t.length() - 1); char l; if (r == '%' || r == '_' || r == '?') { t = t.substring(0, t.length() - 1); if (t.isEmpty()) { if (r == '?') { throw new IllegalArgumentException(on.getOriginKey() + ":value 中字符 " + on.getOriginKey() + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); } l = r; } else { l = t.charAt(t.length() - 1); if (l == '%' || l == '_' || l == '?') { if (l == r) { throw new IllegalArgumentException(on.getOriginKey() + ":value 中字符 " + t + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); } t = t.substring(0, t.length() - 1); } else if (l > 0 && StringUtil.isName(String.valueOf(l))) { l = r; } } if (l == '?') { l = 0; } if (r == '?') { r = 0; } } else { l = r = 0; } if (l <= 0 && r <= 0) { sql += (first ? ON : AND) + lk + (isNot ? NOT : "") + " LIKE " + rk; } else { 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 (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 (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(" : "") + 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(" : "") + lk + (ignoreCase ? ")" : "") + " REGEXP " + (ignoreCase ? "lower(" : "") + rk + (ignoreCase ? ")" : ""); } else { sql += (first ? ON : AND) + lk + (isNot ? NOT : "") + " REGEXP " + (ignoreCase ? "" : "BINARY ") + rk; } } else if ("{}".equals(rt) || "<>".equals(rt)) { String tt = on.getTargetTable(); String ta = on.getTargetAlias(); Map cast = null; if (tt.equals(getTable()) && Objects.equals(ta, getAlias())) { cast = getCast(); } else { boolean find = false; for (Join jn : joinList) { if (tt.equals(jn.getTable()) && Objects.equals(ta, jn.getAlias())) { cast = getCast(); find = true; break; } } if (find == false) { throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + join.getPath() + " 中 JOIN ON 条件中找不到对应的 " + rt + " 不合法!只支持 =, {}, <> 这几种!"); } } boolean isBoolOrNum = SQL.isBooleanOrNumber(cast == null ? null : cast.get(on.getTargetKey())); boolean isIn = "{}".equals(rt); String arrKeyPath = isIn ? rk : lk; String itemKeyPath = isIn ? lk : rk; 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() || 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 ? "( " : "") + gainCondition(isNot, arrKeyPath + " IS NOT NULL AND has(JSONExtractArrayRaw(assumeNotNull(" + arrKeyPath + "))" + ", " + itemKeyPath + ")") + (isNot ? ") " : ""); } else { sql += (first ? ON : AND) + (isNot ? "( " : "") + gainCondition(isNot, arrKeyPath + " IS NOT NULL AND json_contains(" + arrKeyPath + (isBoolOrNum ? ", cast(" + itemKeyPath + " AS CHAR), '$')" : ", concat('\"', " + itemKeyPath + ", '\"'), '$')" ) ) + (isNot ? ") " : ""); } } else { throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + join.getPath() + " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, >, <, >=, <=, !=, $, ~, {}, <> 这几种!"); } } first = false; } } return sql; } protected void onJoinNotRelation(String sql, String quote, Join join, String table, 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 onGainJoinString(Join join) throws UnsupportedOperationException { } protected void onGainCrossJoinString(Join join) throws UnsupportedOperationException { throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } /**新建SQL配置 * @param table * @param request * @param joinList * @param isProcedure * @param callback * @return * @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!"); } Boolean explain = getBoolean(request, KEY_EXPLAIN); if (explain != null && explain && Log.DEBUG == false) { // 不在 config.setExplain 抛异常,一方面处理更早性能更好,另一方面为了内部调用可以绕过这个限制 throw new UnsupportedOperationException("非DEBUG模式, 不允许传 " + KEY_EXPLAIN + " !"); } 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 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 = (SQLConfig) callback.getSQLConfig(method, database, schema, datasource, table); config.setAlias(alias); 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 未解析 if (request.isEmpty()) { // User:{} 这种空内容在查询时也有效 return config; // request.remove(key); 前都可以直接return,之后必须保证 put 回去 } // 对 id, id{}, userId, userId{} 处理,这些只要不为 null 就一定会作为 AND 条件 <<<<<<<<<<<<<<<<<<<<<<<<< String idKey = callback.getIdKey(datasource, database, schema, table); String idInKey = idKey + "{}"; String userIdKey = callback.getUserIdKey(datasource, database, schema, table); String userIdInKey = userIdKey + "{}"; 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! if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { if (newIdIn.contains(d) == false) { newIdIn.add(d); } } } if (newIdIn.isEmpty()) { throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); } idIn = newIdIn; if (method == DELETE || method == PUT) { config.setCount(newIdIn.size()); } } Object id = request.get(idKey); if (id == null && method == POST) { 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"); } } else if (id instanceof String) { if (StringUtil.isEmpty(id, true)) { // 一定没有值 throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".id, true)"); } } else if (id instanceof Subquery) {} else { throw new IllegalArgumentException(idKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); } if (idIn instanceof Collection) { // 共用idIn场景少性能差 boolean contains = false; Collection idList = ((Collection) idIn); for (Object d : idList) { // 不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! if (d != null && id.toString().equals(d.toString())) { contains = true; break; } } if (contains == false) { // empty有效 BaseModel.isEmpty(idIn) == false) { throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); } } if (method == DELETE || method == PUT) { config.setCount(1); } } // 对 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! if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { if (newUserIdIn.contains(d) == false) { newUserIdIn.add(d); } } } if (newUserIdIn.isEmpty()) { throw new NotExistException(TAG + ": newSQLConfig userIdIn instanceof List >> 去掉无效 userId 后 newIdIn.isEmpty()"); } userIdIn = newUserIdIn; } Object userId = 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"); } } else if (userId instanceof String) { if (StringUtil.isEmpty(userId, true)) { // 一定没有值 throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".userId, true)"); } } else if (userId instanceof Subquery) {} else { throw new IllegalArgumentException(userIdKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); } if (userIdIn instanceof Collection) { // 共用 userIdIn 场景少性能差 boolean contains = false; Collection userIds = (Collection) userIdIn; for (Object d : userIds) { // 不用 userIdIn.contains(userId) 因为 userIdIn 里存到很可能是 Integer,userId 又是 Long! if (d != null && userId.toString().equals(d.toString())) { contains = true; break; } } if (contains == false) { // empty有效 BaseModel.isEmpty(userIdIn) == false) { throw new NotExistException(TAG + ": newSQLConfig userIdIn != null && (((List) userIdIn).contains(userId) == false"); } } } // 对 id, id{}, userId, userId{} 处理,这些只要不为 null 就一定会作为 AND 条件 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> String role = 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 = 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_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); request.remove(KEY_NULL); request.remove(KEY_CAST); request.remove(KEY_COMBINE); 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 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< String[] nullKeys = StringUtil.split(nulls); if (nullKeys != null && nullKeys.length > 0) { for (String nk : nullKeys) { if (StringUtil.isEmpty(nk, true)) { throw new IllegalArgumentException(table + ":{ @null: value } 中的字符 '" + nk + "' 不合法!不允许为空!"); } if (request.get(nk) != null) { throw new IllegalArgumentException(table + ":{ @null: value } 中的字符 '" + nk + "' 已在当前对象有非 null 值!不允许对同一个 JSON key 设置不同值!"); } request.put(nk, null); } } // @null >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> // @cast <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< String[] casts = StringUtil.split(cast); Map castMap = null; if (casts != null && casts.length > 0) { castMap = new HashMap<>(casts.length); for (String c : casts) { apijson.orm.Entry p = Pair.parseEntry(c); if (StringUtil.isEmpty(p.getKey(), true)) { throw new IllegalArgumentException(table + ":{} 里的 @cast: 'key0:type0,key1:type1..' 中 '" + c + "' 对应的 key 的字符 '" + p.getKey() + "' 不合法!不允许为空!"); } if (StringUtil.isName(p.getValue()) == false) { throw new IllegalArgumentException(table + ":{} 里的 @cast: 'key0:type0,key1:type1..' 中 '" + c + "' 对应的 type 的字符 '" + p.getValue() + "' 不合法!必须符合类型名称格式!"); } if (castMap.get(p.getKey()) != null) { throw new IllegalArgumentException(table + ":{} 里的 @cast: 'key0:type0,key1:type1..' 中 '" + c + "' 对应的 key 的字符 '" + p.getKey() + "' 已存在!不允许重复设置类型!"); } castMap.put(p.getKey(), p.getValue()); } } // @cast >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 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... boolean ignoreBlankStr = IGNORE_BLANK_STRING_METHOD_LIST != null && IGNORE_BLANK_STRING_METHOD_LIST.contains(method); boolean ignoreEmptyStr = ignoreBlankStr || (IGNORE_EMPTY_STRING_METHOD_LIST != null && IGNORE_EMPTY_STRING_METHOD_LIST.contains(method)); boolean ignoreEmptyOrBlankStr = ignoreEmptyStr || ignoreBlankStr; boolean enableFakeDelete = config.isFakeDelete(); // 已经 remove了 id 和 id{},以及 @key Set set = request.keySet(); // 前面已经判断 request 是否为空 if (method == POST) { // POST操作 if (idIn != null) { throw new IllegalArgumentException(table + ":{" + idInKey + ": value} 里的 key 不合法!POST 请求中不允许传 " + idInKey + " 这种非字段命名 key !必须为 英文字母 开头且只包含 英文字母、数字、下划线的 字段命名!"); } if (userIdIn != null) { throw new IllegalArgumentException(table + ":{" + userIdInKey + ": value} 里的 key 不合法!POST 请求中不允许传 " + userIdInKey + " 这种非字段命名 key !必须为 英文字母 开头且只包含 英文字母、数字、下划线的 字段命名!"); } if (set != null && set.isEmpty() == false) { // 不能直接return,要走完下面的流程 for (String k : set) { if (StringUtil.isName(k) == false) { throw new IllegalArgumentException(table + ":{" + k + ": value} 里的 key 不合法!POST 请求中不允许传 " + k + " 这种非字段命名 key !必须为 英文字母 开头且只包含 英文字母、数字、下划线的 字段命名!"); } } String[] columns = set.toArray(new String[]{}); Collection valueCollection = request.values(); Object[] values = valueCollection == null ? null : valueCollection.toArray(); if (values == null || values.length != columns.length) { throw new Exception("服务器内部错误:\n" + TAG + " newSQLConfig values == null || values.length != columns.length !"); } column = (id == null ? "" : idKey + ",") + (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 数量为准 List items = new ArrayList<>(size); // VALUES(item0, item1, ...) if (id != null) { items.add(id); // idList.get(i)); // 第 0 个就是 id } if (userId != null) { items.add(userId); // idList.get(i)); // 第 1 个就是 userId } for (int j = 0; j < values.length; j++) { 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,其它全是条件!!! // 条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< String[] ws = StringUtil.split(combine); String combineExpr = ws == null || ws.length != 1 ? null : ws[0]; Map> combineMap = new LinkedHashMap<>(); List andList = new ArrayList<>(); List orList = new ArrayList<>(); List notList = new ArrayList<>(); List whereList = new ArrayList<>(); // 强制作为条件且放在最前面优化性能 if (id != null) { tableWhere.put(idKey, id); andList.add(idKey); whereList.add(idKey); } if (idIn != null) { tableWhere.put(idInKey, idIn); andList.add(idInKey); whereList.add(idInKey); } if (userId != null) { tableWhere.put(userIdKey, userId); andList.add(userIdKey); whereList.add(userIdKey); } if (userIdIn != null) { tableWhere.put(userIdInKey, userIdIn); andList.add(userIdInKey); whereList.add(userIdInKey); } if (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; } } } } 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); } if (containNotDeletedValue) { // deletedKey = notDeletedValue String key = deletedKey.toString(); tableWhere.put(key, notDeletedValue); andList.add(key); whereList.add(key); } } } } if (StringUtil.isNotEmpty(combineExpr, true)) { List banKeyList = Arrays.asList(idKey, idInKey, userIdKey, userIdInKey); for (String key : banKeyList) { if (isKeyInCombineExpr(combineExpr, key)) { throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + key + " 不合法!" + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); } } } else if (ws != null) { for (int i = 0; i < ws.length; i++) { // 去除 &,|,! 前缀 String w = ws[i]; if (w != null) { if (w.startsWith("&")) { w = w.substring(1); andList.add(w); } else if (w.startsWith("|")) { if (method == PUT) { throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); } w = w.substring(1); orList.add(w); } else if (w.startsWith("!")) { if (method == PUT) { throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); } w = w.substring(1); notList.add(w); } else { orList.add(w); } if (w.isEmpty()) { throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); } else { if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + ws[i] + " 不合法!" + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); } } whereList.add(w); } // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错! // 去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY if (request.containsKey(w) == false) { // 和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); callback.onMissingKey4Combine(table, request, combine, ws[i], w); if (config instanceof AbstractSQLConfig) { ((AbstractSQLConfig) config).putWarnIfNeed(KEY_COMBINE, table + ":{} 里的 @combine:value 中的 value 里 " + ws[i] + " 对应的条件 " + w + ":value 中 value 必须存在且不能为 null!"); } } } } // 条件 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Map tableContent = new LinkedHashMap(); for (String key : set) { 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 的类型为 JSONMap {} !"); } // 兼容 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)) { tableWhere.put(key, value); } else { tableContent.put(key, value); // 一样 instanceof List ? JSON.toJSONString(value) : value); } } if (combineMap != null) { combineMap.put("&", andList); combineMap.put("|", orList); combineMap.put("!", notList); } config.setCombineMap(combineMap); config.setCombine(combineExpr); 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<>(); List rawList = config.getRaw(); boolean containColumnHavingAnd = rawList != null && rawList.contains(KEY_HAVING_AND); if (containColumnHavingAnd) { throw new IllegalArgumentException(table + ":{ @raw:value } 的 value 里字符 @having& 不合法!" + "@raw 不支持 @having&,请用 @having 替代!"); } // TODO 这段是否必要?如果 @column 只支持分段后的 SQL 片段,也没问题 boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); String rawColumnSQL = null; if (containColumnRaw) { rawColumnSQL = config.gainRawSQL(KEY_COLUMN, column); if (rawColumnSQL != null) { cs.add(rawColumnSQL); } } boolean distinct = rawColumnSQL == null && column != null && column.startsWith(PREFIX_DISTINCT); if (rawColumnSQL == null) { // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) String[] fks = StringUtil.split(distinct ? column.substring(PREFIX_DISTINCT.length()) : column, ";"); if (fks != null) { for (String fk : fks) { if (containColumnRaw) { String rawSQL = config.gainRawSQL(KEY_COLUMN, fk); if (rawSQL != null) { cs.add(rawSQL); continue; } } if (fk.contains("(")) { // fun0(key0,...) cs.add(fk); } else { // key0,key1... String[] ks = StringUtil.split(fk); if (ks != null && ks.length > 0) { cs.addAll(Arrays.asList(ks)); } } } } } // @having, @haivng& <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Object newHaving = having; boolean isHavingAnd = false; Map havingMap = new LinkedHashMap<>(); if (havingAnd != null) { if (having != null) { throw new IllegalArgumentException(table + ":{ @having: value1, @having&: value2 } " + "中 value1 与 value2 不合法!不允许同时传 @having 和 @having& ,两者最多传一个!"); } newHaving = havingAnd; isHavingAnd = true; } String havingKey = (isHavingAnd ? KEY_HAVING_AND : KEY_HAVING); String havingCombine = ""; if (newHaving instanceof String) { String[] havingss = StringUtil.split((String) newHaving, ";"); if (havingss != null) { int ind = -1; for (int i = 0; i < havingss.length; i++) { String havingsStr = havingss[i]; int start = havingsStr == null ? -1 : havingsStr.indexOf("("); int end = havingsStr == null ? -1 : havingsStr.lastIndexOf(")"); if (IS_HAVING_ALLOW_NOT_FUNCTION == false && (start < 0 || start >= end)) { throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 中的第 " + i + " 个字符 '" + havingsStr + "' 不合法!里面没有包含 SQL 函数!必须为 fun(col1,col2..)?val 格式!"); } String[] havings = start >= 0 && end > start ? new String[]{havingsStr} : StringUtil.split(havingsStr); if (havings != null) { for (int j = 0; j < havings.length; j++) { ind ++; String h = havings[j]; if (StringUtil.isEmpty(h, true)) { throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的" + " value 中的第 " + ind + " 个字符 '" + h + "' 不合法!不允许为空!"); } havingMap.put("having" + ind, h); if (isHavingAnd == false && IS_HAVING_DEFAULT_AND == false) { havingCombine += (ind <= 0 ? "" : " | ") + "having" + ind; } } } } } } else if (newHaving instanceof Map) { if (isHavingAnd) { throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 类型不合法!" + "@having&:value 中 value 只能是 String,@having:value 中 value 只能是 String 或 JSONMap !"); } M havingObj = JSON.createJSONObject((Map) newHaving); Set> havingSet = havingObj.entrySet(); for (Entry entry : havingSet) { String k = entry == null ? null : entry.getKey(); Object v = k == null ? null : entry.getValue(); if (v == null) { continue; } if (v instanceof String == false) { throw new IllegalArgumentException(table + ":{ " + havingKey + ":{ " + k + ":value } } 里的" + " value 不合法!类型只能是 String,且不允许为空!"); } if (ignoreEmptyOrBlankStr && StringUtil.isEmpty(v, ignoreBlankStr)) { continue; } if (KEY_COMBINE.equals(k)) { havingCombine = (String) v; } else if (StringUtil.isName(k) == false) { throw new IllegalArgumentException(table + ":{ " + havingKey + ":{ " + k + ":value } } 里的" + " key 对应字符 " + k + " 不合法!必须为 英文字母 开头,且只包含 英文字母、下划线、数字 的合法变量名!"); } else { havingMap.put(k, (String) v); } } } else if (newHaving != null) { throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 类型不合法!" + "@having:value 中 value 只能是 String 或 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 != null && explain); config.setCache(getCache(cache)); config.setDistinct(distinct); config.setColumn(column == null ? null : cs); //解决总是 config.column != null,总是不能得到 * config.setFrom(from); config.setRole(role); config.setId(id); config.setIdIn(idIn); config.setUserId(userId); config.setUserIdIn(userIdIn); config.setNull(nullKeys == null || nullKeys.length <= 0 ? null : new ArrayList<>(Arrays.asList(nullKeys))); config.setCast(castMap); config.setWhere(tableWhere); 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); config.setJson(jsons == null || jsons.length <= 0 ? null : new ArrayList<>(Arrays.asList(jsons))); } finally { // 后面还可能用到,要还原 // id, id{}, userId, userIdIn 条件 if (id != null) { request.put(idKey, id); } if (idIn != null) { request.put(idInKey, idIn); } if (userId != null) { request.put(userIdKey, userId); } if (userIdIn != null) { request.put(userIdInKey, userIdIn); } // 关键词 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; } /** * @param method * @param config * @param joinList * @param callback * @return * @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 等 if (joinList == null || joinList.isEmpty() || RequestMethod.isQueryMethod(method) == false) { return config; } String table; String alias; for (Join join : joinList) { table = join.getTable(); alias = join.getAlias(); //JOIN子查询不能设置LIMIT,因为ON关系是在子查询后处理的,会导致结果会错误 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 (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() + " 不一致!"); } if (joinConfig.getSchema() == null) { joinConfig.setSchema(config.getSchema()); //主表 JOIN 副表,默认 schema 一致 } if (cacheConfig != null) { cacheConfig.setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 } if (isQuery) { config.setKeyPrefix(true); } joinConfig.setMain(false).setKeyPrefix(true); if (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 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 = join.getOnList(); List column = onList == null ? null : new ArrayList<>(onList.size()); //解决 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()); // TODO PostgreSQL 等需要找到具体的 targetTable 对应 targetKey 来加到 SELECT,比直接 SELECT * 性能更好 } } joinConfig.setMethod(GET); // 子查询不能为 SELECT count(*) ,而应该是 SELECT momentId joinConfig.setColumn(column); // 优化性能,不取非必要的字段 if (cacheConfig != null) { cacheConfig.setMethod(GET); // 子查询不能为 SELECT count(*) ,而应该是 SELECT momentId cacheConfig.setColumn(column); // 优化性能,不取非必要的字段 } } join.setJoinConfig(joinConfig); join.setCacheConfig(cacheConfig); } config.setJoinList(joinList); return config; } /**获取客户端实际需要的key * verifyName = true * @param method * @param originKey * @param isTableKey * @param saveLogic 保留逻辑运算符 & | ! * @return */ public static String gainRealKey(RequestMethod method, String originKey , boolean isTableKey, boolean saveLogic) throws Exception { return gainRealKey(method, originKey, isTableKey, saveLogic, true); } /**获取客户端实际需要的key * @param method * @param originKey * @param isTableKey * @param saveLogic 保留逻辑运算符 & | ! * @param verifyName 验证key名是否符合代码变量/常量名 * @return */ 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 || JSONMap.isArrayKey(originKey)) { Log.w(TAG, "getRealKey originKey == null || apijson.JSONMap.isArrayKey(originKey) >> return originKey;"); return 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%' char c = k.isEmpty() ? 0 : k.charAt(k.length() - 1); if (c == '%' || c == '_' || c == '?') { k = k.substring(0, k.length() - 1); char c2 = k.isEmpty() ? 0 : k.charAt(k.length() - 1); if (c2 == '%' || c2 == '_' || c2 == '?') { if (c2 == c) { throw new IllegalArgumentException(originKey + ":value 中字符 " + k + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); } k = k.substring(0, k.length() - 1); } else if (c == '?') { throw new IllegalArgumentException(originKey + ":value 中字符 " + originKey + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); } } key = k; } else if (key.endsWith("~")) {//匹配正则表达式 REGEXP,查询时处理 key = key.substring(0, key.length() - 1); if (key.endsWith("*")) {//忽略大小写 key = key.substring(0, key.length() - 1); } } else if (key.endsWith("%")) {//数字、文本、日期范围 BETWEEN AND key = key.substring(0, key.length() - 1); } else if (key.endsWith("{}")) {//被包含 IN,或者说key对应值处于value的范围内。查询时处理 key = key.substring(0, key.length() - 2); } else if (key.endsWith("}{")) {//被包含 EXISTS,或者说key对应值处于value的范围内。查询时处理 key = key.substring(0, key.length() - 2); } else if (key.endsWith("<>")) {//包含 json_contains,或者说value处于key对应值的范围内。查询时处理 key = key.substring(0, key.length() - 2); } else if (key.endsWith("()")) {//方法,查询完后处理,先用一个Map保存 key = key.substring(0, key.length() - 2); } else if (key.endsWith("@")) {//引用,引用对象查询完后处理。fillTarget中暂时不用处理,因为非GET请求都是由给定的id确定,不需要引用 key = key.substring(0, key.length() - 1); } else if (key.endsWith(">=")) {//比较。查询时处理 key = key.substring(0, key.length() - 2); } else if (key.endsWith("<=")) {//比较。查询时处理 key = key.substring(0, key.length() - 2); } else if (key.endsWith(">")) {//比较。查询时处理 key = key.substring(0, key.length() - 1); } else if (key.endsWith("<")) {//比较。查询时处理 key = key.substring(0, key.length() - 1); } else if (key.endsWith("+")) {//延长,PUT查询时处理 if (method == PUT) {//不为PUT就抛异常 key = key.substring(0, key.length() - 1); } } else if (key.endsWith("-")) {//缩减,PUT查询时处理 if (method == PUT) {//不为PUT就抛异常 key = key.substring(0, key.length() - 1); } } // TODO if (key.endsWith("-")) { // 表示 key 和 value 顺序反过来: value LIKE key ? // 不用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以左边为准 } else { key = Pair.parseEntry(key).getValue();// column 以右边为准 } if (verifyName && StringUtil.isName(key.startsWith("@") ? key.substring(1) : key) == false) { throw new IllegalArgumentException(method + "请求,字符 " + originKey + " 不合法!" + " key:value 中的 key 只能关键词 '@key' 或 'key[长度符][逻辑符][条件符]' 或 PUT 请求下的 'key+' / 'key-' !" + "长度符 只能为 [ - length 和 { - json_length,逻辑符 只能是 & - 与、| - 或、! - 非 !"); } key += len; if (saveLogic && last != null) { key = key + last; } Log.i(TAG, "getRealKey return key = " + key); return key; } public static interface IdCallback { /**为 post 请求新建 id, 只能是 Long 或 String * @param method * @param database * @param schema * @param table * @return */ T newId(RequestMethod method, String database, String schema, String datasource, String table); /**获取主键名 * @param database * @param schema * @param table * @return */ String getIdKey(String database, String schema, String datasource, String table); /**获取 User 的主键名 * @param database * @param schema * @param table * @return */ String getUserIdKey(String database, String schema, String datasource, String table); } 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); /**combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value * @param combine * @param key * @param request */ void onMissingKey4Combine(String name, M request, String combine, String item, String key) throws Exception; } public static Long LAST_ID; static { LAST_ID = System.currentTimeMillis(); } public static abstract class SimpleCallback, L extends List> implements Callback { @SuppressWarnings("unchecked") @Override public T newId(RequestMethod method, String database, String schema, String datasource, String table) { Long id = System.currentTimeMillis(); if (id <= LAST_ID) { id = LAST_ID + 1; // 解决高并发下 id 冲突导致新增记录失败 } LAST_ID = id; return (T) id; } @Override public String getIdKey(String database, String schema, String datasource, String table) { return KEY_ID; } @Override public String getUserIdKey(String database, String schema, String datasource, String table) { return KEY_USER_ID; } @Override 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; } } ================================================ FILE: APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java ================================================ /*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.math.BigDecimal; import java.math.BigInteger; import java.sql.*; import java.time.DayOfWeek; import java.time.LocalDateTime; import java.time.Month; import java.time.Year; import java.util.Date; import java.util.*; import java.util.Map.Entry; import java.util.regex.Pattern; /**executor for query(read) or update(write) MySQL database * @author Lemon */ 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; private int executedSQLCount = 0; @Override public int getGeneratedSQLCount() { return generatedSQLCount; } @Override public int getCachedSQLCount() { return cachedSQLCount; } @Override 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; } /** * 缓存 Map */ protected Map> cacheMap = new HashMap<>(); /**保存缓存 * @param sql key * @param list value * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null */ @Override 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 */ @Override public List getCache(String sql, SQLConfig config) { return cacheMap.get(sql); } /**获取缓存 * @param sql key * @param position * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null * @return */ @Override 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; } 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) { if (sql == null) { Log.i(TAG, "removeList sql == null >> return;"); return; } cacheMap.remove(sql); } @Override public ResultSet executeQuery(@NotNull Statement statement, String sql) throws Exception { ResultSet rs = statement.executeQuery(sql); return rs; } @Override public int executeUpdate(@NotNull Statement statement, String sql) throws Exception { int c = statement.executeUpdate(sql); return c; } @Override public ResultSet execute(@NotNull Statement statement, String sql) throws Exception { statement.execute(sql); ResultSet rs = statement.getResultSet(); return rs; } /**执行SQL * @param config * @return * @throws Exception */ @Override public M execute(@NotNull SQLConfig config, boolean unknownType) throws Exception { long executedSQLStartTime = System.currentTimeMillis(); 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(); M result; if (isExplain == false) { generatedSQLCount ++; } long startTime = System.currentTimeMillis(); Log.d(TAG, "\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + "\n已生成 " + generatedSQLCount + " 条 SQL" + "\nexecute startTime = " + startTime + "\ndatabase = " + StringUtil.get(config.getDatabase()) + "; schema = " + StringUtil.get(config.getSchema()) + "; sql = \n" + sql + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); ResultSet rs = null; List resultList = null; Map childMap = null; Map keyMap = null; try { if (unknownType) { if (isExplain == false) { //只有 SELECT 才能 EXPLAIN executedSQLCount ++; executedSQLStartTime = System.currentTimeMillis(); } Statement statement = getStatement(config); rs = execute(statement, sql); int updateCount = statement.getUpdateCount(); if (isExplain == false) { executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } 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 { RequestMethod method = config.getMethod(); switch (method) { case POST: case PUT: case DELETE: if (isExplain == false) { //只有 SELECT 才能 EXPLAIN executedSQLCount ++; executedSQLStartTime = System.currentTimeMillis(); } int updateCount = executeUpdate(config); if (isExplain == false) { executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } if (updateCount <= 0) { throw new IllegalAccessException("没权限访问或对象不存在!"); // NotExistException 会被 catch 转为成功状态 } // updateCount>0时收集结果。例如更新操作成功时,返回count(affected rows)、id字段 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()); } 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: List cache = getCache(sql, config); result = getCacheItem(cache, position, config); Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result); if (result != null) { 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; } if (isExplain == false) { //只有 SELECT 才能 EXPLAIN executedSQLCount ++; executedSQLStartTime = System.currentTimeMillis(); } rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults if (isExplain == false) { executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } break; 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 parser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!")); } 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); } else { // final boolean cache = config.getCount() != 1; // Log.d(TAG, "select cache = " + cache + "; resultList" + (resultList == null ? "=" : "!=") + "null"); try { // 设置初始容量为查到的数据量,解决频繁扩容导致的延迟,貌似只有 rs.last 取 rs.getRow() ? 然后又得 rs.beforeFirst 重置位置以便下方取值 rs.last(); //移到最后一行 resultList = new ArrayList<>(rs.getRow()); rs.beforeFirst(); } catch (Throwable e) { Log.e(TAG, "try { rs.last(); resultList = new ArrayList<>(rs.getRow()); rs.beforeFirst(); >> } catch (Throwable e) = " + e.getMessage()); int capacity; if (config.getId() != null) { // id:Object 一定是 AND 条件,最终返回数据最多就这么多 capacity = 1; } else { Object idIn = config.getIdIn(); if (idIn instanceof Collection) { // id{}:[] 一定是 AND 条件,最终返回数据最多就这么多 capacity = ((Collection) idIn).size(); } else { // 预估容量 capacity = config.getCount() <= 0 ? AbstractParser.MAX_QUERY_COUNT : config.getCount(); if (capacity > 100) { // 有 WHERE 条件,条件越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 Map> combine = config.getCombineMap(); List andList = combine == null ? null : combine.get("&"); 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(); capacity /= Math.pow(1.5, Math.log10(capacity) + andCondCount + ((orCondCount <= 0 ? 0 : 2.0d/orCondCount) // 1: 2.3, 2: 1.5, 3: 1.3, 4: 1.23, 5: 1.18 + (notCondCount/5.0d) // 1: 1.08, 2: 1.18, 3: 1.28, 4: 1.38, 1.50 + (groupCount <= 0 ? 0 : 10.0d/groupCount)) // 1: 57.7, 7.6, 3: 3.9, 4: 2.8, 5: 2.3 + havingCount ); capacity += 1; // 避免正好比需要容量少一点点导致多一次扩容,大量数据 System.arrayCopy } } } resultList = new ArrayList<>(capacity); } int index = -1; long startTime2 = System.currentTimeMillis(); ResultSetMetaData rsmd = rs.getMetaData(); final int length = rsmd.getColumnCount(); sqlResultDuration += System.currentTimeMillis() - startTime2; //