{"record":{"id":"9c73625b2c9a7917","repo":"Tencent/APIJSON","slug":"jointype-table-originkey-9c7362","errorCode":null,"errorMessage":"joinType + \"/.../\" + table + \"/\" + originKey + \" 中字符 \" + l.getKey() + \" 不合法！必须符合字段命名格式！","messagePattern":"joinType \\+ \"/\\.\\.\\./\" \\+ table \\+ \"/\" \\+ originKey \\+ \" 中字符 \" \\+ l\\.getKey\\(\\) \\+ \" 不合法！必须符合字段命名格式！","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/Join.java","lineNumber":349,"sourceCode":"\t\t\t\tsetRelateType(\"<\");\n\t\t\t\tk = originKey.substring(0, originKey.length() - 1);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tsetRelateType(\"\");\n\t\t\t\tk = originKey;\n\t\t\t}\n\n\t\t\tif (k != null && (k.contains(\"&\") || k.contains(\"|\"))) {\n\t\t\t\tthrow new UnsupportedOperationException(joinType + \"/.../\" + table + \"/\" + originKey + \" 中字符 \" + k + \" 不合法！与或非逻辑符仅支持 '!' 非逻辑符 ！\");\n\t\t\t}\n\n\t\t\t//TODO if (c3 == '-') { // 表示 key 和 value 顺序反过来: value LIKE key\n\n\t\t\tLogic l = new Logic(k);\n\t\t\tsetLogic(l);\n\n\t\t\tif (StringUtil.isName(l.getKey()) == false) {\n\t\t\t\tthrow new IllegalArgumentException(joinType + \"/.../\" + table + \"/\" + originKey + \" 中字符 \" + l.getKey() + \" 不合法！必须符合字段命名格式！\");\n\t\t\t}\n\n\t\t\tsetKey(l.getKey());\n\t\t}\n\n  }\n\n}\n","sourceCodeStart":331,"sourceCodeEnd":358,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/Join.java#L331-L358","documentation":"Final join-key validation: after stripping operators and the optional '!' logic prefix (new Logic(k)), the bare field name must pass StringUtil.isName — i.e. start with a letter and contain only letters/digits/underscores (a valid identifier). Keys with spaces, dots, dashes, leading digits, or non-ASCII characters throw IllegalArgumentException, because the name will be interpolated into generated SQL as a column identifier.","triggerScenarios":"A join reference key like \"user.id@\" (dot), \"my-name@\" (dash), \"1id@\" (leading digit), or \"名字@\" (non-ASCII) after operator stripping leaves l.getKey() failing StringUtil.isName, e.g. from ORM joins. Also fires when only operators remain (empty key after stripping) or a key consists solely of placeholders.","commonSituations":"Using JSON-path style 'user.id' in join keys; column aliases with dashes leaking into keys; generating join config from user input without identifier validation; using SQL-quoted identifiers (\"`col`\") in key names.","solutions":["Use plain identifiers in join keys: letters, digits, underscore, starting with a letter — e.g. \"userId@\":\"/Moment/userId\".","Move qualifying prefixes into the reference path (the '/table/field@' part), not the key: \"id@\":\"/Moment/userId\".","If the actual DB column contains special chars, alias it in the structure config (@column) rather than in the join key; sanitize any programmatically generated keys with a ^[A-Za-z][A-Za-z0-9_]*$ check."],"exampleFix":"// before\n\"User\":{\"user.id@\":\"/Moment/userId\"}\n// after\n\"User\":{\"id@\":\"/Moment/userId\"}","handlingStrategy":"validation","validationCode":"private static final Pattern NAME = Pattern.compile(\"^[A-Za-z][A-Za-z0-9_]*$\");\nString bareKeyAfterOperators(String k) { return k.replaceFirst(\"(\\\\{\\\\}|\\\\[\\\\]|<>|\\\\$|\\\\*?~|>=|<=|>|<|!)\", \"\"); }\nvoid assertJoinKeyIsName(String k) {\n  String bare = bareKeyAfterOperators(k);\n  if (!NAME.matcher(bare).matches())\n    throw new IllegalArgumentException(\"join key '\" + k + \"' reduces to '\" + bare + \"' which is not a valid identifier\");\n}","typeGuard":"function isJoinKeyName(k) { return /^[A-Za-z][A-Za-z0-9_]*$/.test(k.replace(/[\\{\\}\\[\\]<>$~%_?>=!*]+$/, '')); }","tryCatchPattern":null,"preventionTips":["Join keys must be plain identifiers (letter first, then letters/digits/underscore) — no dots, dashes, spaces, or backticks.","Put table qualification in the reference path ('/Moment/userId@'), never in the key.","Sanitize any join config built from user input with a ^[A-Za-z][A-Za-z0-9_]*$ check before sending."],"tags":["apijson","join","identifier-validation","request-syntax"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}