{"record":{"id":"1cad8f804195ab86","repo":"Tencent/APIJSON","slug":"combine-1cad8f","errorCode":null,"errorMessage":"{}:{ @combine:'{}' } 中字符 '{}' 不合法！左边缺少 & | 逻辑连接符！逻辑连接符 & | 左右必须各一个相邻空格！空格不能多也不能少！不允许首尾有空格，也不允许连续空格！左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格！","messagePattern":"(.+?):(.+?)' \\} 中字符 '(.+?)' 不合法！左边缺少 & \\| 逻辑连接符！逻辑连接符 & \\| 左右必须各一个相邻空格！空格不能多也不能少！不允许首尾有空格，也不允许连续空格！左括号 \\( 的右边 和 右括号 \\) 的左边 都不允许有相邻空格！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java","lineNumber":3595,"sourceCode":"\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (c == '!') {\n\t\t\t\t\tlast = i <= 0 ? 0 : s.charAt(i - 1);  // & | 后面跳过了空格\n\n\t\t\t\t\tchar next = i >= n - 1 ? 0 : s.charAt(i + 1);\n\t\t\t\t\tif (last == ' ' || last == '(') {\n\t\t\t\t\t\tif (next == ' ') {\n\t\t\t\t\t\t\tthrow new IllegalArgumentException(errPrefix + \" 中字符 '\" + s.substring(0, i + 1)\n\t\t\t\t\t\t\t+ \"' 不合法！非逻辑符 '!' 右边多了一个空格 ' ' ！非逻辑符 '!' \" +\n\t\t\t\t\t\t\t\t\t\"右边不允许任何相邻空格 ' '，也不允许 ')' '&' '|' 中任何一个！\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (next == ')' || next == '&' || next == '!') {\n\t\t\t\t\t\t\tthrow new IllegalArgumentException(errPrefix + \" 中字符 '\" + s.substring(0, i + 1)\n\t\t\t\t\t\t\t+ \"' 不合法！非逻辑符 '!' 右边多了一个字符 '\"\n\t\t\t\t\t\t\t\t\t+ next + \"' ！非逻辑符 '!' 右边不允许任何相邻空格 ' '，也不允许 ')' '&' '|' 中任何一个！\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (i > 0 && lastLogic <= 0 && last != '(') {\n\t\t\t\t\t\t\tthrow new IllegalArgumentException(errPrefix + \" 中字符 '\" + s.substring(i)\n\t\t\t\t\t\t\t+ \"' 不合法！左边缺少 & | 逻辑连接符！逻辑连接符 & | 左右必须各一个相邻空格！空格不能多也不能少！\"\n\t\t\t\t\t\t\t+ \"不允许首尾有空格，也不允许连续空格！左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格！\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (next == '(') {\n\t\t\t\t\t\tresult += SQL.NOT;\n\t\t\t\t\t\tlastLogic = c;\n\t\t\t\t\t}\n\t\t\t\t\telse if (last <= 0 || last == ' ' || last == '(') {\n\t\t\t\t\t\tisNot = true;\n\t\t\t\t\t\t//\t\t\t\t\tlastLogic = c;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tkey += c;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (c == '(') {","sourceCodeStart":3577,"sourceCodeEnd":3613,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java#L3577-L3613","documentation":"When '!' appears in operator position (after a space or '('), it must itself follow a logic operator: the guard i > 0 && lastLogic <= 0 && last != '(' throws when no & or | has been seen since the last term. I.e., '!' cannot be the first connector in a group or expression — it is a modifier on a term, not a standalone joiner, so \"!a & b\" at the start is fine (last=='(' or start) but \"a !b\" (nothing between a and !b) is not.","triggerScenarios":"@combine:\"id !name\" (missing & before the NOT term), or \"(a !b)\" inside a group. lastLogic is only set >0 when & | ! (before '(') was consumed, so a bare \"key !key\" sequence leaves it 0 and throws at line 3595 with the remainder s.substring(i).","commonSituations":"Assuming '!key' alone negates-and-joins like 'and not'; deleting an '&' between a term and a '!term' during edits; building expressions by concatenation and dropping the operator before a NOT'd optional filter.","solutions":["Always precede a '!term' with '& ' or '| ' unless it is the very first term or right after '(': \"id & !name\".","Treat ! as part of the operand, and keep the normal operator between operands.","Validate with the strict grammar regex before sending (see validation code)."],"exampleFix":"// before\n{\"@combine\":\"id !deleted\"}\n// after\n{\"@combine\":\"id & !deleted\"}","handlingStrategy":"validation","validationCode":"// every '!term' (except at start / right after '(') must be preceded by '& ' or '| '\nString norm = combine.replaceAll(\"\\\\([\\\\w!&|() ]*\\\\)\", \"K\");\nif (norm.matches(\".*[\\\\w)]\\\\s*!.*\") || norm.matches(\".*[\\\\w)]!.*\"))\n    throw new IllegalArgumentException(\"'!' term missing & or | before it\");","typeGuard":"function notTermsPreceded(s: string): boolean {\n  const norm = s.replace(/\\([\\w!&|() ]*\\)/g, 'K');\n  return !/[\\wK)]\\s*!/.test(norm); // no ! directly after a term or group without an operator\n}","tryCatchPattern":null,"preventionTips":["Treat '!x' as part of an operand — keep a real & or | between operands.","When deleting an operator, delete the whole NOT-term, not just the &.","Grammar-test generated combine strings, including NOT terms."],"tags":["apijson","combine","not-operator","syntax-error"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}