{"record":{"id":"d5141d5489a1423e","repo":"Tencent/APIJSON","slug":"head-alias-column-value-valu","errorCode":null,"errorMessage":"HEAD请求: 字符 {alias} 不合法！预编译模式下 @column:value 中 value里面用 , 分割的每一项 column:alias 中 column 必须是1个单词！如果有alias，则alias也必须为1个单词！并且不要有多余的空格！","messagePattern":"HEAD请求: 字符 (.+?) 不合法！预编译模式下 @column:value 中 value里面用 , 分割的每一项 column:alias 中 column 必须是1个单词！如果有alias，则alias也必须为1个单词！并且不要有多余的空格！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java","lineNumber":2322,"sourceCode":"\t\t\t\tList<String> raw = getRaw();\n\t\t\t\tboolean containRaw = raw != null && raw.contains(KEY_COLUMN);\n\n\t\t\t\tfor (String c : column) {\n\t\t\t\t\tif (containRaw) {\n\t\t\t\t\t\t// 由于 HashMap 对 key 做了 hash 处理，所以 get 比 containsValue 更快\n\t\t\t\t\t\tif (\"\".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) {  // newSQLConfig<T, M, L> 提前处理好的\n\t\t\t\t\t\t\t//排除@raw中的值，以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错\n\t\t\t\t\t\t\t//column.remove(c);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tint index = c.lastIndexOf(\":\"); //StringUtil.split返回数组中，子项不会有null\n\t\t\t\t\tString origin = index < 0 ? c : c.substring(0, index);\n\t\t\t\t\tString alias = index < 0 ? null : c.substring(index + 1);\n\n\t\t\t\t\tif (alias != null && StringUtil.isName(alias) == false) {\n\t\t\t\t\t\tthrow new IllegalArgumentException(\"HEAD请求: 字符 \" + alias\n\t\t\t\t\t\t\t\t+ \" 不合法！预编译模式下 @column:value 中 value里面用 , 分割的每一项\"\n\t\t\t\t\t\t\t\t+ \" column:alias 中 column 必须是1个单词！如果有alias，则alias也必须为1个单词！并且不要有多余的空格！\");\n\t\t\t\t\t}\n\n\t\t\t\t\tif (StringUtil.isName(origin) == false) {\n\t\t\t\t\t\tint start = origin.indexOf(\"(\");\n\t\t\t\t\t\tif (start < 0 || origin.lastIndexOf(\")\") <= start) {\n\t\t\t\t\t\t\tthrow new IllegalArgumentException(\"HEAD请求: 字符\" + origin\n\t\t\t\t\t\t\t\t\t+ \" 不合法！预编译模式下 @column:value 中 value里面用 , 分割的每一项\"\n\t\t\t\t\t\t\t\t\t+ \" column:alias 中 column 必须是1个单词！\"\n\t\t\t\t\t\t\t\t\t+ \"如果有alias，则 alias 也必须为1个单词！并且不要有多余的空格！\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start > 0 && StringUtil.isName(origin.substring(0, start)) == false) {\n\t\t\t\t\t\t\tthrow new IllegalArgumentException(\"HEAD请求: 字符 \" + origin.substring(0, start)\n\t\t\t\t\t\t\t\t\t+ \" 不合法！预编译模式下 @column:value 中 value里面用 , 分割的每一项\"\n\t\t\t\t\t\t\t\t\t+ \" column:alias 中 column 必须是1个单词！如果有alias，则alias也必须为1个单词！并且不要有多余的空格！\");\n\t\t\t\t\t\t}","sourceCodeStart":2304,"sourceCodeEnd":2340,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java#L2304-L2340","documentation":"Thrown while building the SELECT COUNT clause for a HEAD/HEADS request in prepared mode (AbstractSQLConfig.gainColumnString). Each comma-separated item in @column:value is split at the last ':' into origin and alias; if an alias exists and StringUtil.isName(alias) is false (i.e. it is not a single identifier word), this IllegalArgumentException is thrown before any SQL is generated. It exists to prevent SQL injection through crafted alias segments in HEAD count queries.","triggerScenarios":"A HEAD/HEADS request whose table object contains \"@column\":\"id : my alias\" or \"@column\":\"userId:u1 u2\" — any alias containing spaces, punctuation, operators, or multiple words. Also triggered by URLs like /head/User#@column={\"@column\":\"date_format(date,'%Y-%m-%d'):my alias\"} when not whitelisted via @raw.","commonSituations":"Frontend developers copying a SQL expression like DATE_FORMAT(date,'%Y-%m-%d %H:%i:%s') AS formattedDate into @column for a count endpoint; forgetting that HEAD only supports column or single function expressions, not arbitrary aliases; trailing spaces after the colon introduced by string concatenation in client code.","solutions":["Remove the alias or make it a single word: \"@column\":\"userId:uid\" instead of \"userId:my uid\".","Remove all spaces around ':' — \"id:alias\" not \"id : alias\" or \"id: alias\".","For expressions needing colons or spaces (e.g. date_format(date,'%Y-%m-%d %H:%i')), whitelist the whole expression server-side via RAW_MAP / @raw so the parser skips it.","For a plain count, drop @column entirely — count(*) is generated when column is null or contains a comma."],"exampleFix":"// before\n{\"User\":{\"@column\":\"name : display name\"}}\n// after\n{\"User\":{\"@column\":\"name:displayName\"}}","handlingStrategy":"validation","validationCode":"const NAME = /^[A-Za-z][A-Za-z0-9_]*$/;\nconst okHeadColumn = (col) => col.split(',').every(item => { const i = item.lastIndexOf(':'); if (i < 0) return NAME.test(item); return NAME.test(item.slice(0, i)) && NAME.test(item.slice(i + 1)); });\nif (!okHeadColumn(req.User['@column'])) throw new Error('bad HEAD @column');","typeGuard":"null","tryCatchPattern":"catch (IllegalArgumentException e) { respond 400 with e.getMessage(); log offending @column value; }","preventionTips":["Never put spaces inside @column items or around ':'","Reserve aliases for single-word identifiers","For HEAD counts, prefer omitting @column (count(*))"],"tags":["apijson","sql","input-validation","prepared-statement","head-request"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}