{"record":{"id":"aed1f6d9c3ecd6c9","repo":"Tencent/APIJSON","slug":"fun-example-function","errorCode":null,"errorMessage":"字符 {fun} 不合法！预编译模式下 {example} 中 function 必须符合小写英文单词的 SQL 函数名格式！","messagePattern":"字符 (.+?) 不合法！预编译模式下 (.+?) 中 function 必须符合小写英文单词的 SQL 函数名格式！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java","lineNumber":2562,"sourceCode":"\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) {\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","sourceCodeStart":2544,"sourceCodeEnd":2580,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java#L2544-L2580","documentation":"When SQL_FUNCTION_MAP is null or empty (backend function allowlist not configured), parseSQLExpression falls back to StringUtil.isName(fun): the function name before '(' must be a single identifier word. This error means the allowlist is unconfigured AND the function token is not a plain name.","triggerScenarios":"Backend never called the static initializer that populates SQL_FUNCTION_MAP (or deliberately cleared it), and a client sends an expression whose token before '(' is not a plain word, e.g. \"schema.fn(x)\" or \"fn(x)(y)\". Any non-word token before '(' triggers it in this mode.","commonSituations":"Custom APIJSON forks that lazily initialize the allowlist; unit tests constructing AbstractSQLConfig without full static init; expressions with operators before '(' like \"sum(a)+max(b)\" parsed as one item.","solutions":["Ensure the backend initializes SQL_FUNCTION_MAP (it is populated by default in the static block; verify your subclass/launcher did not skip it).","Make the token before '(' a bare lowercase identifier.","Send one function per ';'-item instead of merging tokens.","For non-standard names, add them to SQL_FUNCTION_MAP server-side."],"exampleFix":"// before (allowlist empty and token not a word)\n{\"User\":{\"@column\":\"my-schema.fn(id)\"}}\n// after\n{\"User\":{\"@column\":\"fn(id)\"}}","handlingStrategy":"try-catch","validationCode":"const FUN=/^[A-Za-z][A-Za-z0-9_]*$/;\nconst tok=expr.slice(0,expr.indexOf('('));\nif(!FUN.test(tok))throw new Error('function token must be a bare word');","typeGuard":"null","tryCatchPattern":"catch IllegalArgumentException; verify backend initialization of SQL_FUNCTION_MAP in server logs, then retry with a plain function name","preventionTips":["Ensure backend static init runs (default SQL_FUNCTION_MAP is filled)","One function per expression item","No operators or prefixes before '('"],"tags":["apijson","sql","input-validation","function-allowlist","configuration"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}