{"record":{"id":"0ae849e6d03aa1f7","repo":"Tencent/APIJSON","slug":"expression-having-column-value","errorCode":null,"errorMessage":"字符串 ${expression} 不合法！预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\" 中 column?value 必须符合正则表达式 ${PATTERN_FUNCTION} 且不包含连续减号 -- ！不允许空格！","messagePattern":"字符串 (.+?) 不合法！预编译模式下 @having:\"column\\?value;function\\(arg0,arg1,\\.\\.\\.\\)\\?value\\.\\.\\.\" 中 column\\?value 必须符合正则表达式 (.+?) 且不包含连续减号 -- ！不允许空格！","errorType":"validation","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java","lineNumber":1768,"sourceCode":"\tprotected String gainHavingItem(String quote, String table, String alias\n\t\t\t, String key, String expression, boolean containRaw) throws Exception {\n\t\t//fun(arg0,arg1,...)\n\t\tif (containRaw) {\n\t\t\tString rawSQL = gainRawSQL(KEY_HAVING, expression);\n\t\t\tif (rawSQL != null) {\n\t\t\t\treturn rawSQL;\n\t\t\t}\n\t\t}\n\n\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 + \" 不合法！\"","sourceCodeStart":1750,"sourceCodeEnd":1786,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java#L1750-L1786","documentation":"In prepared mode, a @having expression without parentheses must fully match PATTERN_FUNCTION (the column?value form). No spaces, no consecutive dashes --, only the whitelisted operator/comparison shape. This is the primary injection guard for HAVING since values cannot be bound parameters inside expressions.","triggerScenarios":"\"@having\": \"amount > 0\" (spaces), \"@having\": \"a--b>1\" (double dash), \"@having\": \"name='x' or 1=1\" — any parentheses-free expression that fails the regex.","commonSituations":"Pretty-printed JSON with spaces inside the value; frontend template inserting user text unescaped; trying SQL syntax (OR, quotes) that the pattern intentionally forbids.","solutions":["Remove all spaces: \"@having\": \"amount>0\"","Use the supported key(condition) form and APIJSON operators instead of raw SQL keywords","For legitimately complex expressions use @raw + server-side RAW_MAP"],"exampleFix":"// before\n{\"@having\": \"amount > 0\"}\n// after\n{\"@having\": \"amount>0\"}","handlingStrategy":"validation","validationCode":"// mirror of the server check for the no-paren form: no spaces, no '--'\nconst okSimple = s => /^[A-Za-z0-9_.]+(>=|<=|>|<|!=|=)[^\\s'\\-]*$/.test(s) && !s.includes('--');\nconst hv = obj['@having'];\nif (typeof hv === 'string' && !hv.includes('(') && !okSimple(hv)) {\n  throw new Error(`@having '${hv}' fails column?value pattern (no spaces, no --)`);\n}","typeGuard":"const isSimpleHavingValid = s => typeof s === 'string' && !/\\s/.test(s) && !s.includes('--');","tryCatchPattern":"try { await api.get(req); } catch (e) { if (e.message.includes('PATTERN_FUNCTION')) req['User']['@having'] = req['User']['@having'].replace(/\\s+/g, ''); else throw e; }","preventionTips":["Never minify by trimming spaces client-side only after an error — build clean strings from the start","Forbid spaces in @having at the input layer"],"tags":["apijson","having","regex-validation","sql-injection"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}