{"record":{"id":"8f311fd687a7de10","repo":"Tencent/APIJSON","slug":"errprefix-s-column-c","errorCode":null,"errorMessage":"{errPrefix} 中字符 '{s}' 不合法！其中 '{column}' 重复引用，次数 {count} 已超过最大值，必须在 0-{maxCombineKeyCount} 内！","messagePattern":"(.+?) 中字符 '(.+?)' 不合法！其中 '(.+?)' 重复引用，次数 (.+?) 已超过最大值，必须在 0-(.+?) 内！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java","lineNumber":3525,"sourceCode":"\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\n\t\t\t\t\t\tresult += \"( \" + gainCondition(isNot, wi) + \" )\";\n\t\t\t\t\t\tisNot = false;\n\t\t\t\t\t\tfirst = false;\n\t\t\t\t\t}\n\n\t\t\t\t\tkey = \"\";\n\t\t\t\t\tlastLogic = 0;\n\n\t\t\t\t\tif (isOver) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n","sourceCodeStart":3507,"sourceCodeEnd":3543,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java#L3507-L3543","documentation":"usedKeyCountMap tracks how many times each column is referenced while parsing @combine. After incrementing, if count exceeds maxCombineKeyCount (>0), the request is rejected. This is a per-key cap — stricter and earlier than the aggregate ratio check (error 152) — preventing a single column from being repeated dozens of times in one expression.","triggerScenarios":"@combine:\"a & a & a & a & a\" with maxCombineKeyCount set (e.g. 5 by default in hardened deployments): the 6th reference to 'a' throws at line 3525. Distinct keys are unaffected; only repetition of one column counts.","commonSituations":"Generated expressions that fan one flag into many branches (\"(a & b) | (a & c) | (a & d) ...\"); security tuning that lowered maxCombineKeyCount; upgrading to an APIJSON version where per-key caps became configurable/enabled.","solutions":["Reference each key at most maxCombineKeyCount times; hoist common terms: \"a & (b | c | d)\" instead of repeating a.","Raise maxCombineKeyCount server-side if repetition is legitimate.","Lint generated combine strings for duplicate-key counts before sending (see validation code)."],"exampleFix":"// before\n{\"@combine\":\"(a & b) | (a & c) | (a & d)\"}\n// after\n{\"@combine\":\"a & (b | c | d)\"}","handlingStrategy":"validation","validationCode":"Map<String, Long> counts = Arrays.stream(combine.replaceAll(\"[()!]\", \" \").split(\"\\\\s*[&|]\\\\s*\"))\n    .collect(Collectors.groupingBy(k -> k.split(\":\")[0], Collectors.counting()));\nif (counts.values().stream().anyMatch(c -> MAX_COMBINE_KEY_COUNT > 0 && c > MAX_COMBINE_KEY_COUNT))\n    throw new IllegalStateException(\"key referenced too often\");","typeGuard":"function keyCountsWithin(combine: string, max: number): boolean {\n  const m = new Map<string, number>();\n  for (const t of combine.replace(/[()!]/g, ' ').split(/\\s*[&|]\\s*/)) {\n    const k = t.split(':')[0]; m.set(k, (m.get(k) ?? 0) + 1);\n    if (m.get(k)! > max) return false;\n  }\n  return true;\n}","tryCatchPattern":null,"preventionTips":["Factor shared keys out with parentheses: a & (b | c) not (a&b) | (a&c).","Set per-key reference budgets in your expression generator.","Read maxCombineKeyCount from server config."],"tags":["apijson","combine","limits","expression-parsing"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}