{"record":{"id":"6a41f52ec03d4e1b","repo":"Tencent/APIJSON","slug":"expression-key-value-value-sql","errorCode":null,"errorMessage":"字符 {expression} 不合法！{key}:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式！","messagePattern":"字符 (.+?) 不合法！(.+?):value 中 value 里的 SQL函数必须为 function\\(arg0,arg1,\\.\\.\\.\\) 这种格式！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java","lineNumber":2555,"sourceCode":"\t\t\t\t\texample = key + \":\\\"column0!=0;column1+3*2<=10;function0(arg0,arg1,...)>1;function1(...)%5<=3...\\\"\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t//有函数,但不是窗口函数\n\t\t\tint overIndex = expression.indexOf(\")OVER(\");  // 传参不传空格，拼接带空格  \") OVER (\");\n\t\t\tint againstIndex = expression.indexOf(\")AGAINST(\");  // 传参不传空格，拼接带空格  \") AGAINST (\");\n\t\t\tboolean containOver = overIndex > 0 && overIndex < expression.length() - \")OVER(\".length();\n\t\t\tboolean containAgainst = againstIndex > 0 && againstIndex < expression.length() - \")AGAINST(\".length();\n\n\t\t\tif (containOver && containAgainst) {\n\t\t\t\tthrow new IllegalArgumentException(\"字符 \" + expression + \" 不合法！预编译模式下 \" + example\n\t\t\t\t\t\t+ \" 中 function 必须符合小写英文单词的 SQL 函数名格式！不能同时存在窗口函数关键词 OVER 和全文索引关键词 AGAINST！\");\n\t\t\t}\n\n\t\t\tif (containOver == false && containAgainst == false) {\n\t\t\t\tint end = expression.lastIndexOf(')');\n\t\t\t\tif (start >= end) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"字符 \" + expression + \" 不合法！\"\n\t\t\t\t\t\t\t+ key + \":value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式！\");\n\t\t\t\t}\n\t\t\t\tString fun = expression.substring(0, start);\n\t\t\t\tif (fun.isEmpty() == false) {\n\t\t\t\t\tif (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) {\n\t\t\t\t\t\tif (StringUtil.isName(fun) == false) {\n\t\t\t\t\t\t\tthrow new IllegalArgumentException(\"字符 \" + fun + \" 不合法！预编译模式下 \" + example\n\t\t\t\t\t\t\t\t\t+ \" 中 function 必须符合小写英文单词的 SQL 函数名格式！\");\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (SQL_FUNCTION_MAP.containsKey(fun) == false) {\n\t\t\t\t\t\tthrow new IllegalArgumentException(\"字符 \" + fun + \" 不合法！预编译模式下 \" + example\n\t\t\t\t\t\t\t\t+ \" 中 function 必须符合小写英文单词的 SQL 函数名格式！且必须是后端允许调用的 SQL 函数!\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tString s = expression.substring(start + 1, end);\n\t\t\t\tboolean distinct = s.startsWith(PREFIX_DISTINCT);\n\t\t\t\tif (distinct) {","sourceCodeStart":2537,"sourceCodeEnd":2573,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java#L2537-L2573","documentation":"For an @column (or similar) expression containing '(', parseSQLExpression takes start as the first '(' and end as the last ')'; if start >= end the expression cannot be of the required function(arg0,arg1,...) shape and this IllegalArgumentException fires. It catches unbalanced or degenerate parenthesis placement before any SQL is emitted.","triggerScenarios":"\"@column\":\"count(\" , \"@column\":\"count())\" , \"@column\":\"()\" , or \"@column\":\"concat(a,b))\" where lastIndexOf(')') lands before/at the first '('. Also \"@column\":\"now(\" after client-side truncation.","commonSituations":"Client string truncation (fixed-length input fields) chopping off closing parens; hand-concatenating function calls and losing a paren; expressions like \"cast(now() as date\" where the closing paren was dropped in templating.","solutions":["Balance the parentheses: exactly wrap args as fun(arg0,arg1,...).","Add client-side validation that the expression's parens balance and the last char after the final ')' is ':' or end-of-string.","Prefer server-side @raw for expressions the DSL struggles to express.","Check for truncation at transport boundaries (URL encoding, field length limits)."],"exampleFix":"// before\n{\"User\":{\"@column\":\"concat(name,' - ',id\"}}\n// after\n{\"User\":{\"@column\":\"concat(name,' - ',id)\"}}","handlingStrategy":"validation","validationCode":"function balanced(s){let d=0;for(const c of s){if(c==='(')d++;if(c===')')d--;if(d<0)return false}return d===0}\nconst start=expr.indexOf('('),end=expr.lastIndexOf(')');\nif(start<0||start>=end||!balanced(expr))throw new Error('expect fn(args) form');","typeGuard":"null","tryCatchPattern":"catch IllegalArgumentException; alert user to paren error and keep the input for editing","preventionTips":["Paren-balance lint on expression builders","Avoid truncating @column values","Template with tested snippets, not string surgery"],"tags":["apijson","sql","input-validation","syntax","parentheses"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}