{"record":{"id":"8efaa5b1ceb71ec2","repo":"Tencent/APIJSON","slug":"jointype-table-originkey","errorCode":null,"errorMessage":"joinType + \"/.../\" + table + \"/\" + originKey + \" 中字符 \" + originKey + \" 不合法！join:'.../refKey'\" + \" 中 refKey 必须以 @ 结尾！","messagePattern":"joinType \\+ \"/\\.\\.\\./\" \\+ table \\+ \"/\" \\+ originKey \\+ \" 中字符 \" \\+ originKey \\+ \" 不合法！join:'\\.\\.\\./refKey'\" \\+ \" 中 refKey 必须以 @ 结尾！","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/Join.java","lineNumber":275,"sourceCode":"\t\t\tthis.targetAlias = targetAlias;\n\t\t}\n\t\tpublic String getTargetAlias() {\n\t\t\treturn targetAlias;\n\t\t}\n\t\tpublic String getTargetKey() {\n\t\t\treturn targetKey;\n\t\t}\n\t\tpublic void setTargetKey(String targetKey) {\n\t\t\tthis.targetKey = targetKey;\n\t\t}\n\n\n\t\tpublic void setKeyAndType(String joinType, String table, @NotNull String originKey) throws Exception { //id, id@, id{}@, contactIdList<>@ ...\n\t\t\tif (originKey.endsWith(\"@\")) {\n\t\t\t\toriginKey = originKey.substring(0, originKey.length() - 1);\n\t\t\t}\n\t\t\telse { //TODO 暂时只允许 User.id = Moment.userId 字段关联，不允许 User.id = 82001 这种\n\t\t\t\tthrow new IllegalArgumentException(joinType + \"/.../\" + table + \"/\" + originKey + \" 中字符 \" + originKey + \" 不合法！join:'.../refKey'\" + \" 中 refKey 必须以 @ 结尾！\");\n\t\t\t}\n\n\t\t\tString k;\n\n\t\t\tif (originKey.endsWith(\"{}\")) {\n\t\t\t\tsetRelateType(\"{}\");\n\t\t\t\tk = originKey.substring(0, originKey.length() - 2);\n\t\t\t}\n\t\t\telse if (originKey.endsWith(\"<>\")) {\n\t\t\t\tsetRelateType(\"<>\");\n\t\t\t\tk = originKey.substring(0, originKey.length() - 2);\n\t\t\t}\n\t\t\telse if (originKey.endsWith(\"$\")) {  // key%$:\"a\" -> key LIKE '%a%'; key?%$:\"a\" -> key LIKE 'a%'; key_?$:\"a\" -> key LIKE '_a'; key_%$:\"a\" -> key LIKE '_a%'\n\t\t\t\tk = originKey.substring(0, originKey.length() - 1);\n\t\t\t\tchar c = k.isEmpty() ? 0 : k.charAt(k.length() - 1);\n\n\t\t\t\tString t = \"$\";\n\t\t\t\tif (c == '%' || c == '_' || c == '?') {","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/Join.java#L257-L293","documentation":"Join.setKeyAndType parses each key inside a @join table's field map. APIJSON join references must be of the form '.../refKey@' where the trailing '@' marks the value as a reference to the current table's field (e.g. \"join\":\"/User/id@\", User:{ \"id@\":\"/Moment/userId\" }). If originKey does not end with '@', APIJSON cannot tell which local field to bind — literal constant comparisons in join keys are deliberately unsupported (see the TODO in source), so it throws IllegalArgumentException.","triggerScenarios":"A request specifies join config like \"User\":{\"name\":\"/Moment/name\"} or \"join\":\"/User/id\" — the key after stripping operators does not terminate with '@'. Only 'id@', 'id{}@', 'contactIdList<>@' style keys are accepted.","commonSituations":"Assuming join keys can bind literal values (User.id = 82001) — unsupported by design; forgetting the '@' suffix when hand-writing join objects; converting an older APIJSON 3.x request to 4.x join syntax; using '@combine'-style keys inside a join table map.","solutions":["Append '@' and point the key at an existing field of the main table: \"User\":{\"id@\":\"/Moment/userId\"} — and ensure the referenced field exists in the outer object.","Put constant conditions directly as normal where-clauses on the joined table's object (which supports literal values) instead of in the reference key.","Check spelling of the refKey chain: format is 'alias/tableKey/field@' or per docs '.../refKey@'."],"exampleFix":"// before\n{\"Moment\":{...},\"join\":\"/User/id\",\"User\":{\"id\":\"/Moment/userId\"}}\n// after\n{\"Moment\":{...},\"join\":\"/User/id@\",\"User\":{\"id@\":\"/Moment/userId\"}}","handlingStrategy":"validation","validationCode":"private static final Pattern JOIN_REF = Pattern.compile(\"^!?\\\\w+(\\\\{(\\\\}|\\\\[\\\\])|<>|\\\\$|~|\\\\*~|>=|<=|>|<)?@$\");\nvoid checkJoinKeys(JSONObject joinTableObj) {\n  for (String k : joinTableObj.keySet())\n    if (!JOIN_REF.matcher(k).matches() && !k.startsWith(\"@\"))\n      throw new IllegalArgumentException(\"join key '\" + k + \"' must end with @ (reference), e.g. 'id@'\");\n}","typeGuard":"function isJoinRefKey(k) { return /^!?[A-Za-z]\\w*(@|\\{\\}@|<>@|\\$@|~@|\\*~@|>=@|<=@|>@|<@)$/.test(k); }","tryCatchPattern":null,"preventionTips":["Always end join reference keys with '@' — literal-value join keys are unsupported by design; put constants in the joined table's where object.","Centralize join-config construction in one helper that appends and validates the '@' suffix."],"tags":["apijson","join","request-syntax","query-config"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}