{"record":{"id":"f9217526f17b0683","repo":"Tencent/APIJSON","slug":"errprefix-key","errorCode":null,"errorMessage":"{errPrefix} 中字符 '{key}' 不合法！左边缺少 & | 其中一个逻辑连接符！","messagePattern":"(.+?) 中字符 '(.+?)' 不合法！左边缺少 & \\| 其中一个逻辑连接符！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java","lineNumber":3482,"sourceCode":"\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;\n\t\t\t\t\t\tint keyIndex = column.indexOf(\":\");\n\t\t\t\t\t\tcolumn = keyIndex > 0 ? column.substring(0, keyIndex) : column;\n\t\t\t\t\t\tObject value = conditionMap.get(column);\n\t\t\t\t\t\tString wi = \"\";\n\t\t\t\t\t\tif (value == null && conditionMap.containsKey(column) == false) { // 兼容@null\n\t\t\t\t\t\t\tisNot = false; // 以占位表达式为准\n\t\t\t\t\t\t\tsize++; // 兼容 key 数量判断","sourceCodeStart":3464,"sourceCodeEnd":3500,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java#L3464-L3500","documentation":"In @combine parsing, a non-empty key was just terminated (by space, ')' or end) but this is not the first term and lastLogic <= 0, meaning no '&' or '|' was seen since the previous term. Every term after the first must be joined by a logical operator, so two keys appearing back-to-back (separated only by a parenthesis or start of a group) is a syntax error.","triggerScenarios":"@combine:\"id name\" is caught earlier as a space-with-empty-key variant, but \"(id)(name)\", \"(id) name\", or \"id!name\" shapes reach here: the key text accumulates past a boundary without an operator. Also \"a & (b c)\" — inside the group, b is followed by c with no operator, so when c terminates and first==false with lastLogic==0, it throws at line 3482.","commonSituations":"Assuming juxtaposition means AND (as in some query DSLs) instead of requiring '&'; missing operator after a closing parenthesis; copy-paste deleting an operator between keys.","solutions":["Insert '&' or '|' between every pair of terms, including right after ')': \"(id | name) & tag\".","Use parentheses plus explicit operators everywhere; never rely on adjacency.","Validate with a grammar check before sending (see validation code)."],"exampleFix":"// before\n{\"@combine\":\"(id)(name)\"}\n// after\n{\"@combine\":\"(id) & (name)\"}","handlingStrategy":"validation","validationCode":"// after tokenizing, ensure no two key/group tokens are adjacent without an operator between them\nString normalized = combine.replaceAll(\"\\\\([^)]*\\\\)\", \"K\").replaceAll(\"[!\\\\w]+\", \"K\");\nif (normalized.matches(\".*KK.*\")) throw new IllegalArgumentException(\"adjacent terms without & or |\");","typeGuard":"function noAdjacentTerms(s: string): boolean {\n  const norm = s.replace(/\\([^)]*\\)/g, 'K').replace(/[!\\w]+/g, 'K');\n  return !/KK/.test(norm);\n}","tryCatchPattern":null,"preventionTips":["Assume nothing is implicit AND — the grammar requires explicit & or |.","Insert operators when deleting a key from an expression builder.","Grammar-check generated strings."],"tags":["apijson","combine","expression-parsing","syntax-error"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}