{"record":{"id":"2b8055f88b440eaa","repo":"Tencent/APIJSON","slug":"errprefix-s-key-allcount-2b8055","errorCode":null,"errorMessage":"{errPrefix} 中字符 '{s}' 不合法！其中 key 数量 {allCount} / 条件键值对数量 {size} = {allCount/size} 已超过 最大倍数，必须在条件键值对数量 0-{maxCombineRatio} 倍内！","messagePattern":"(.+?) 中字符 '(.+?)' 不合法！其中 key 数量 (.+?) / 条件键值对数量 (.+?) = (.+?) 已超过 最大倍数，必须在条件键值对数量 0-(.+?) 倍内！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java","lineNumber":3512,"sourceCode":"\t\t\t\t\t\tint keyIndex = column.indexOf(\":\");\n\t\t\t\t\t\tcolumn = keyIndex > 0 ? column.substring(0, keyIndex) : column;\n\t\t\t\t\t\tObject value = conditionMap.get(column);\n\t\t\t\t\t\tString wi = \"\";\n\t\t\t\t\t\tif (value == null && conditionMap.containsKey(column) == false) { // 兼容@null\n\t\t\t\t\t\t\tisNot = false; // 以占位表达式为准\n\t\t\t\t\t\t\tsize++; // 兼容 key 数量判断\n\t\t\t\t\t\t\twi = keyIndex > 0 ? key.substring(keyIndex + 1) : \"\";\n\t\t\t\t\t\t\tif (StringUtil.isEmpty(wi)) {\n\t\t\t\t\t\t\t\tthrow new IllegalArgumentException(errPrefix + \" 中字符 '\"\n\t\t\t\t\t\t\t\t\t\t+ key + \"' 对应的条件键值对 \" + column + \":value 不存在！\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twi = isHaving ? gainHavingItem(quote, table, alias, column, (String) value, containRaw)\n\t\t\t\t\t\t\t\t\t: gainWhereItem(column, value, method, verifyName);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (1.0f*allCount/size > maxCombineRatio && maxCombineRatio > 0) {\n\t\t\t\t\t\t\tthrow new IllegalArgumentException(errPrefix + \" 中字符 '\" + s + \"' 不合法！\"\n\t\t\t\t\t\t\t\t\t+ \"其中 key 数量 \" + allCount + \" / 条件键值对数量 \" + size + \" = \" + (1.0f*allCount/size)\n\t\t\t\t\t\t\t\t\t+ \" 已超过 最大倍数，必须在条件键值对数量 0-\" + maxCombineRatio + \" 倍内！\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (StringUtil.isEmpty(wi, true)) {  // 转成 1=1 ?\n\t\t\t\t\t\t\tthrow new IllegalArgumentException(errPrefix + \" 中字符 '\" + key\n\t\t\t\t\t\t\t\t\t+ \"' 对应的 \" + column + \":value 不是有效条件键值对！\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tInteger count = usedKeyCountMap.get(column);\n\t\t\t\t\t\tcount = count == null ? 1 : count + 1;\n\t\t\t\t\t\tif (count > maxCombineKeyCount && maxCombineKeyCount > 0) {\n\t\t\t\t\t\t\tthrow new IllegalArgumentException(errPrefix + \" 中字符 '\" + s + \"' 不合法！\"\n\t\t\t\t\t\t\t\t\t+ \"其中 '\" + column + \"' 重复引用，次数 \" + count\n\t\t\t\t\t\t\t\t\t+ \" 已超过最大值，必须在 0-\" + maxCombineKeyCount + \" 内！\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tusedKeyCountMap.put(column, count);\n","sourceCodeStart":3494,"sourceCodeEnd":3530,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java#L3494-L3530","documentation":"After each accepted key in @combine, the ratio allCount/size (referenced keys vs. total condition pairs) is compared to maxCombineRatio. Exceeding it throws. This complements error 150: instead of an absolute cap, it bounds how top-heavy an expression is relative to the condition map — a protection against expressions that reference a few keys an enormous number of times (e.g. giant generated formulas), which is a parse-time DoS vector.","triggerScenarios":"A condition map of size 3 (\"a\",\"b\",\"c\") with @combine:\"a & a & a & a ... & b | c\" — allCount grows per reference, size stays 3; once allCount/3 > maxCombineRatio (e.g. 5.0) it throws. size can also be incremented on the fly for placeholder keys (the size++ for @null-compat), slightly changing the ratio.","commonSituations":"Repeating the same key many times in generated expressions; low maxCombineRatio configured server-side after hardening; upgrading APIJSON to a version that introduced ratio limits so previously-working requests start failing.","solutions":["Reference each key once (or a few times); do not repeat keys in @combine.","Raise maxCombineRatio in server config if the workload legitimately reuses keys heavily.","Simplify the logic — deeply repeated terms usually reduce via parentheses reordering or by moving fixed conditions out of @combine (unstated keys default to AND)."],"exampleFix":"// before\n{\"@combine\":\"a & a & a & a & a & a | b\"}\n// after\n{\"@combine\":\"a | b\",\"c\":1,\"d\":1}","handlingStrategy":"validation","validationCode":"int refs = combine.split(\"[&|]\", -1).length; // per-term references\nint size = condKeys.size();\nif (MAX_COMBINE_RATIO > 0 && (float) refs / size > MAX_COMBINE_RATIO) throw new IllegalStateException(\"combine ratio too high\");","typeGuard":"function withinRatio(refs: number, size: number, max: number): boolean { return max <= 0 || refs / size <= max; }","tryCatchPattern":"catch (IllegalArgumentException e) { /* simplify expression, remove repeated key references, retry */ }","preventionTips":["Reference each key once where possible.","Hoist common subexpressions with parentheses instead of repeating keys.","Know your deployment's maxCombineRatio before generating expressions."],"tags":["apijson","combine","limits","dos-protection"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}