{"record":{"id":"1fa701cef11a1954","repo":"Tencent/APIJSON","slug":"expression-having-value-value-sql","errorCode":null,"errorMessage":"字符 ${expression} 不合法！@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式！","messagePattern":"字符 (.+?) 不合法！@having:value 中 value 里的 SQL函数必须为 function\\(arg0,arg1,\\.\\.\\.\\) 这种格式！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java","lineNumber":1778,"sourceCode":"\t\tif (expression.length() > 100) {\n\t\t\tthrow new UnsupportedOperationException(\"@having:value 的 value 中字符串 \" + expression + \" 不合法！\"\n\t\t\t\t\t+ \"不允许传超过 100 个字符的函数或表达式！请用 @raw 简化传参！\");\n\t\t}\n\n\t\tint start = expression.indexOf(\"(\");\n\t\tif (start < 0) {\n\t\t\tif (isPrepared() && PATTERN_FUNCTION.matcher(expression).matches() == false) {\n\t\t\t\tthrow new UnsupportedOperationException(\"字符串 \" + expression + \" 不合法！\"\n\t\t\t\t\t\t+ \"预编译模式下 @having:\\\"column?value;function(arg0,arg1,...)?value...\\\"\"\n\t\t\t\t\t\t+ \" 中 column?value 必须符合正则表达式 \" + PATTERN_FUNCTION + \" 且不包含连续减号 -- ！不允许空格！\");\n\t\t\t}\n\t\t\t\n\t\t\treturn parseSQLExpression(KEY_HAVING, expression, containRaw, false, null);\n\t\t}\n\n\t\tint end = expression.lastIndexOf(\")\");\n\t\tif (start >= end) {\n\t\t\tthrow new IllegalArgumentException(\"字符 \" + expression + \" 不合法！\"\n\t\t\t\t\t+ \"@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式！\");\n\t\t}\n\n\t\tString method = expression.substring(0, start);\n\t\tif (method.isEmpty() == false) {\n\t\t\tif (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) {\n\t\t\t\tif (StringUtil.isName(method) == false) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"字符 \" + method + \" 不合法！\"\n\t\t\t\t\t\t\t+ \"预编译模式下 @having:\\\"column?value;function(arg0,arg1,...)?value...\\\"\"\n\t\t\t\t\t\t\t+ \" 中 function 必须符合小写英文单词的 SQL 函数名格式！\");\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (SQL_FUNCTION_MAP.containsKey(method) == false) {\n\t\t\t\tthrow new IllegalArgumentException(\"字符 \" + method + \" 不合法！\"\n\t\t\t\t\t\t+ \"预编译模式下 @column:\\\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\\\"\"\n\t\t\t\t\t\t+ \" 中 function 必须符合小写英文单词的 SQL 函数名格式！且必须是后端允许调用的 SQL 函数!\");\n\t\t\t}\n\t\t}","sourceCodeStart":1760,"sourceCodeEnd":1796,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java#L1760-L1796","documentation":"The @having expression contains '(' but the last ')' is at or before the first '(' — the parser cannot extract a function(arg0,arg1,...) call, so it throws. Every SQL function used in @having must be written as name(args) with the closing paren after the opening one.","triggerScenarios":"\"@having\": \"max(\" , \"@having\": \"sum(1,2\" , or \"@having\": \")sum(\" — malformed or truncated function syntax where indexOf('(') >= lastIndexOf(')').","commonSituations":"String concatenation cuts off the tail of the expression; user typos; templating that drops the closing parenthesis; nested quotes swallowing characters.","solutions":["Write complete function calls: \"@having\": \"max(amount)>100\"","Validate balanced parentheses client-side before sending","Check that JSON string escaping did not eat a closing paren"],"exampleFix":"// before\n{\"@having\": \"sum(amount>1000\"}\n// after\n{\"@having\": \"sum(amount)>1000\"}","handlingStrategy":"validation","validationCode":"const hv = obj['@having'];\nif (typeof hv === 'string' && hv.includes('(')) {\n  const open = hv.indexOf('('), close = hv.lastIndexOf(')');\n  if (open >= close) throw new Error(`@having '${hv}' must be function(arg0,...) with ')' after '('`);\n}","typeGuard":"const hasBalancedFuncSyntax = s => typeof s === 'string' && (!s.includes('(') || s.indexOf('(') < s.lastIndexOf(')'));","tryCatchPattern":"try { await api.get(req); } catch (e) { if (e.message.includes('function(arg0,arg1,...)')) highlightBrokenExpression(e.message); else throw e; }","preventionTips":["Assemble function expressions from tested fragments, not string slicing","Run a paren-balance lint on generated @having values"],"tags":["apijson","having","syntax"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}