Tencent/APIJSON · error · IllegalArgumentException

字符 {fun} 不合法!预编译模式下 {example} 中 function 必须符合小写英文单词的 SQL 函数名

Error message

字符 {fun} 不合法!预编译模式下 {example} 中 function 必须符合小写英文单词的 SQL 函数名格式!

What it means

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.

Source

Thrown at APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java:2562

			boolean containOver = overIndex > 0 && overIndex < expression.length() - ")OVER(".length();
			boolean containAgainst = againstIndex > 0 && againstIndex < expression.length() - ")AGAINST(".length();

			if (containOver && containAgainst) {
				throw new IllegalArgumentException("字符 " + expression + " 不合法!预编译模式下 " + example
						+ " 中 function 必须符合小写英文单词的 SQL 函数名格式!不能同时存在窗口函数关键词 OVER 和全文索引关键词 AGAINST!");
			}

			if (containOver == false && containAgainst == false) {
				int end = expression.lastIndexOf(')');
				if (start >= end) {
					throw new IllegalArgumentException("字符 " + expression + " 不合法!"
							+ key + ":value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!");
				}
				String fun = expression.substring(0, start);
				if (fun.isEmpty() == false) {
					if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) {
						if (StringUtil.isName(fun) == false) {
							throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example
									+ " 中 function 必须符合小写英文单词的 SQL 函数名格式!");
						}
					} else if (SQL_FUNCTION_MAP.containsKey(fun) == false) {
						throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example
								+ " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!");
					}
				}

				String s = expression.substring(start + 1, end);
				boolean distinct = s.startsWith(PREFIX_DISTINCT);
				if (distinct) {
					s = s.substring(PREFIX_DISTINCT.length());
				}

				// 解析函数内的参数
				String ckeys[] = parseArgsSplitWithComma(s, false, containRaw, allowAlias);

				String suffix = expression.substring(end + 1); //:contactCount

View on GitHub (pinned to 5284052872)

Solutions

  1. Ensure the backend initializes SQL_FUNCTION_MAP (it is populated by default in the static block; verify your subclass/launcher did not skip it).
  2. Make the token before '(' a bare lowercase identifier.
  3. Send one function per ';'-item instead of merging tokens.
  4. For non-standard names, add them to SQL_FUNCTION_MAP server-side.

Example fix

// before (allowlist empty and token not a word)
{"User":{"@column":"my-schema.fn(id)"}}
// after
{"User":{"@column":"fn(id)"}}
Defensive patterns

Strategy: try-catch

Validate before calling

const FUN=/^[A-Za-z][A-Za-z0-9_]*$/;
const tok=expr.slice(0,expr.indexOf('('));
if(!FUN.test(tok))throw new Error('function token must be a bare word');

Type guard

null

Try / catch

catch IllegalArgumentException; verify backend initialization of SQL_FUNCTION_MAP in server logs, then retry with a plain function name

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Tencent/APIJSON@5284052872 (2026-08-14). Data as JSON: /api/errors/aed1f6d9c3ecd6c9. Report an issue: GitHub.