{"record":{"id":"c1c1a48d4db6a9ff","repo":"Tencent/APIJSON","slug":"fun-example-function-c1c1a4","errorCode":null,"errorMessage":"字符 {fun} 不合法！预编译模式下 {example} 中 function 必须符合小写英文单词的 SQL 函数名格式！且必须是后端允许调用的 SQL 函数!","messagePattern":"字符 (.+?) 不合法！预编译模式下 (.+?) 中 function 必须符合小写英文单词的 SQL 函数名格式！且必须是后端允许调用的 SQL 函数!","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java","lineNumber":2566,"sourceCode":"\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) {\n\t\t\t\t\ts = s.substring(PREFIX_DISTINCT.length());\n\t\t\t\t}\n\n\t\t\t\t// 解析函数内的参数\n\t\t\t\tString ckeys[] = parseArgsSplitWithComma(s, false, containRaw, allowAlias);\n\n\t\t\t\tString suffix = expression.substring(end + 1); //:contactCount\n\t\t\t\tString alias = null;\n\t\t\t\tif (allowAlias) {\n\t\t\t\t\tint index = suffix.lastIndexOf(\":\");\n\t\t\t\t\talias = index < 0 ? \"\" : suffix.substring(index + 1); //contactCount","sourceCodeStart":2548,"sourceCodeEnd":2584,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java#L2548-L2584","documentation":"parseSQLExpression enforces a function allowlist: when SQL_FUNCTION_MAP is populated (the default), any function name before '(' that is not a key in that map is rejected with this message naming the offending token. Only backend-approved SQL functions may appear in client expressions.","triggerScenarios":"\"@column\":\"ifnull(name,'x')\" where 'ifnull' was not added to SQL_FUNCTION_MAP; \"@column\":\"version()\"; any DB-specific function the backend administrator did not whitelist; also sending a differently-cased key than the exact registered one.","commonSituations":"Frontend uses a MySQL function the default map lacks (e.g. database-specific or new-in-8.0 functions); after upgrading APIJSON the map contents changed; multi-DB deployments where one DB's function is not registered for the other.","solutions":["Use a function already in the default map (count/sum/max/min/avg/date_format/concat/...).","Ask the backend administrator to add the function to SQL_FUNCTION_MAP (AbstractSQLConfig static block or your SQLConfig subclass).","Re-express the computation with allowed primitives or compute client-side.","Define the whole expression as a @raw entry server-side."],"exampleFix":"// before\n{\"User\":{\"@column\":\"ifnull(name,'x')\"}}\n// after — server adds SQL_FUNCTION_MAP.put(\"ifnull\", \"\"); then the same request succeeds\n{\"User\":{\"@column\":\"ifnull(name,'x')\"}}","handlingStrategy":"validation","validationCode":"const ALLOWED = new Set(['count','sum','max','min','avg','date_format','concat' /* mirror backend SQL_FUNCTION_MAP */]);\nconst fn = expr.slice(0, expr.indexOf('('));\nif (!ALLOWED.has(fn)) throw new Error('function not allowed: ' + fn);","typeGuard":"function isAllowedFn(e, allow) { const i = e.indexOf('('); return i > 0 && allow.has(e.slice(0, i)); }","tryCatchPattern":"catch IllegalArgumentException mentioning backend allowlist; report to backend admin to whitelist or rewrite with allowed functions","preventionTips":["Mirror the backend allowlist in client constants","Check the default SQL_FUNCTION_MAP before using exotic functions","For new functions, change backend config first, then client"],"tags":["apijson","sql","security","function-allowlist","input-validation"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}