{"record":{"id":"c0e9279250c6334d","repo":"Tencent/APIJSON","slug":"latest-value-item-value","errorCode":null,"errorMessage":"预编译模式下 @latest:value 中 ${item} 不合法! value 里面用 , 分割的每一项必须是 column 且其中 column 必须是 英语单词！并且不要有多余的空格！","messagePattern":"预编译模式下 @latest:value 中 (.+?) 不合法! value 里面用 , 分割的每一项必须是 column 且其中 column 必须是 英语单词！并且不要有多余的空格！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java","lineNumber":1925,"sourceCode":"\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tString latest = StringUtil.trim(getLatest());\n\n\t\tString[] keys = StringUtil.split(latest);\n\t\tif (keys == null || keys.length <= 0) {\n\t\t\treturn StringUtil.isEmpty(joinLatest, true) ? \"\" : (hasPrefix ? \" LATEST ON \" : \"\") + joinLatest;\n\t\t}\n\n\t\tfor (int i = 0; i < keys.length; i++) {\n\t\t\tString item = keys[i];\n\t\t\tString origin = item;\n\n\t\t\tif (isPrepared()) { //不能通过 ? 来代替，SELECT 'id','name' 返回的就是 id:\"id\", name:\"name\"，而不是数据库里的值！\n\t\t\t\t//这里既不对origin trim，也不对 ASC/DESC ignoreCase，希望前端严格传没有任何空格的字符串过来，减少传输数据量，节约服务器性能\n\t\t\t\tif (StringUtil.isName(origin) == false) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"预编译模式下 @latest:value 中 \" + item + \" 不合法! value 里面用 , 分割的\"\n\t\t\t\t\t\t\t+ \"每一项必须是 column 且其中 column 必须是 英语单词！并且不要有多余的空格！\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tkeys[i] = gainKey(origin);\n\t\t}\n\n\t\treturn (hasPrefix ? \" LATEST ON \" : \"\") + StringUtil.concat(StringUtil.get(keys), joinLatest, \", \");\n\t}\n\n\t@Override\n\tpublic String getPartition() {\n\t\treturn partition;\n\t}\n\tpublic AbstractSQLConfig<T, M, L> setPartition(String... conditions) {\n\t\treturn setPartition(StringUtil.get(conditions));\n\t}\n\t@Override","sourceCodeStart":1907,"sourceCodeEnd":1943,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java#L1907-L1943","documentation":"Prepared-mode validation of @latest:value: each comma-separated item must be a single word identifier (StringUtil.isName). LATEST ON columns cannot be bound parameters, so anything that is not a clean name is refused.","triggerScenarios":"\"@latest\": \"created at\" , \"@latest\": \"t.id\" (dot), \"@latest\": \"id;--\" — an item failing isName.","commonSituations":"Using qualified table.column names; pasting SQL; whitespace introduced by building the string with array join.","solutions":["Use bare column names: \"@latest\": \"createdAt\"","Strip spaces/symbols when generating the list programmatically","Backend: disable ambiguity by configuring RAW_MAP for any unavoidable expression"],"exampleFix":"// before\n{\"@latest\": \"log.createdAt\"}\n// after\n{\"@latest\": \"createdAt\"}","handlingStrategy":"validation","validationCode":"for (const item of String(obj['@latest'] ?? '').split(',')) {\n  if (item && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(item)) throw new Error(`@latest item '${item}' must be a single word`);\n}","typeGuard":"const isSingleIdentifier = s => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s);","tryCatchPattern":"try { await api.get(req); } catch (e) { if (e.message.includes('@latest')) req['@latest'] = req['@latest'].replace(/\\s/g, ''); else throw e; }","preventionTips":["Never send qualified names (tbl.col) in @latest; set the table object instead","Lint clause-list fields for spaces"],"tags":["apijson","latest","prepared-statement"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}