{"record":{"id":"ab2213af16cdfc3b","repo":"Tencent/APIJSON","slug":"errprefix-s-key","errorCode":null,"errorMessage":"{errPrefix} 中字符 '{s}' 不合法！空格 ' ' 左边缺少条件 key ！逻辑连接符 & | 左右必须各一个相邻空格！空格不能多也不能少！不允许首尾有空格，也不允许连续空格！左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格！","messagePattern":"(.+?) 中字符 '(.+?)' 不合法！空格 ' ' 左边缺少条件 key ！逻辑连接符 & \\| 左右必须各一个相邻空格！空格不能多也不能少！不允许首尾有空格，也不允许连续空格！左括号 \\( 的右边 和 右括号 \\) 的左边 都不允许有相邻空格！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java","lineNumber":3475,"sourceCode":"\t\t\tint depth = 0;\n\t\t\tint allCount = 0;\n\n\t\t\tint i = 0;\n\n\t\t\tchar lastLogic = 0;\n\t\t\tchar last = 0;\n\t\t\tboolean first = true;\n\t\t\tboolean isNot = false;\n\n\t\t\tString key = \"\";\n\t\t\twhile (i <= n) {  // \"date> | (contactIdList<> & (name*~ | tag&$))\"\n\t\t\t\tboolean isOver = i >= n;\n\t\t\t\tchar c = isOver ? 0 : s.charAt(i);\n\t\t\t\tboolean isBlankOrRightParenthesis = c == ' ' || c == ')';\n\t\t\t\tif (isOver || isBlankOrRightParenthesis) {\n\t\t\t\t\tboolean isEmpty = StringUtil.isEmpty(key, true);\n\t\t\t\t\tif (isEmpty && last != ')') {\n\t\t\t\t\t\tthrow new IllegalArgumentException(errPrefix + \" 中字符 '\" + (isOver ? s : s.substring(i))\n\t\t\t\t\t\t\t\t+ \"' 不合法！\" + (c == ' ' ? \"空格 ' ' \" : \"右括号 ')'\") + \" 左边缺少条件 key ！逻辑连接符 & | 左右必须各一个相邻空格！\"\n\t\t\t\t\t\t\t\t+ \"空格不能多也不能少！不允许首尾有空格，也不允许连续空格！左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格！\");\n\t\t\t\t\t}\n\n\t\t\t\t\tif (isEmpty == false) {\n\t\t\t\t\t\tif (first == false && lastLogic <= 0) {\n\t\t\t\t\t\t\tthrow new IllegalArgumentException(errPrefix + \" 中字符 \"\n\t\t\t\t\t\t\t\t\t+ \"'\" + s.substring(i - key.length() - (isOver ? 1 : 0))\n\t\t\t\t\t\t\t\t\t+ \"' 不合法！左边缺少 & | 其中一个逻辑连接符！\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tallCount ++;\n\t\t\t\t\t\tif (allCount > maxCombineCount && maxCombineCount > 0) {\n\t\t\t\t\t\t\tthrow new IllegalArgumentException(errPrefix + \" 中字符 '\" + s + \"' 不合法！\"\n\t\t\t\t\t\t\t\t\t+ \"其中 key 数量 \" + allCount + \" 已超过最大值，必须在条件键值对数量 0-\" + maxCombineCount + \" 内！\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tString column = key;","sourceCodeStart":3457,"sourceCodeEnd":3493,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java#L3457-L3493","documentation":"During character-by-character parsing of @combine, when the scanner hits a space or ')' (or end of input) and no condition key has been accumulated (key empty) while the previous char was not ')', the expression is malformed: a space or closing parenthesis appeared with no key to its left. This covers cases like \"a & & b\", \"a &\", \"( )\", \"a &  b\" (double space collapses to an empty key).","triggerScenarios":"@combine:\"id & & name\", \"id & \", \"( )\", \"id &  name\" (two spaces — the second space terminates an empty key), or a ')' right after an operator: \"id & )\". errPrefix contains the offending remainder s.substring(i).","commonSituations":"Hand-written combine strings; concatenating optional parts so operators end up adjacent (\"a & \" + \"& b\"); double spaces after an operator because the grammar requires exactly one space each side and developers add more; leftover operators after deleting a key from a builder template.","solutions":["Rewrite so every & and | is surrounded by exactly one space and always sits between two keys or groups: \"id & (name | tag)\".","Never chain two operators; use parentheses for grouping instead.","Build combine strings from key lists with a join helper (see validation code) instead of string concatenation.","Validate with a strict grammar regex before sending (see validation code)."],"exampleFix":"// before\n{\"@combine\":\"id & & name\"}\n// after\n{\"@combine\":\"id & name\"}","handlingStrategy":"validation","validationCode":"static final Pattern COMBINE = Pattern.compile(\"^[!\\\\w]+(\\\\s[&|]\\\\s[!\\\\w]+|\\\\s[&|]\\\\s\\\\([\\\\w&|!() ]+\\\\))*$\"); // then also check no \"  \", no \"()\"\nif (!COMBINE.matcher(combine).matches() || combine.contains(\"  \") || combine.contains(\"()\")) throw new IllegalArgumentException(\"bad @combine\");","typeGuard":"function isWellFormedCombine(s: string): boolean {\n  return !/^\\s|\\s$/.test(s) && !/\\s{2}/.test(s) && !/[&(]\\s|\\s[)!]/.test(s.replace(/ & | \\| /g, '')) === false;\n}","tryCatchPattern":null,"preventionTips":["Every operator between exactly two operands; no operator chains.","Generate combine from a key list with a join helper.","Test generated expressions against a grammar fixture before shipping."],"tags":["apijson","combine","expression-parsing","syntax-error"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}