{"record":{"id":"b118cbc7ebd60214","repo":"apache/druid","slug":"expected-single-element","errorCode":null,"errorMessage":"Expected single element","messagePattern":"Expected single element","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/segment/join/table/RowBasedIndexBuilder.java","lineNumber":159,"sourceCode":"      final long rangeThreshold = Math.max(\n          INT_ARRAY_SMALL_SIZE_OK,\n          Math.min(Integer.MAX_VALUE, INT_ARRAY_SPACE_SAVINGS_FACTOR * index.size())\n      );\n\n      if (range > 0 && range < rangeThreshold) {\n        final int[] indexAsArray = new int[Ints.checkedCast(range)];\n        Arrays.fill(indexAsArray, IndexedTable.Index.NOT_FOUND);\n\n        // Safe to cast to Long2ObjectMap because the constructor always uses one for long-typed keys.\n        final ObjectIterator<Long2ObjectMap.Entry<IntSortedSet>> entries =\n            ((Long2ObjectMap<IntSortedSet>) ((Map) index)).long2ObjectEntrySet().iterator();\n\n        while (entries.hasNext()) {\n          final Long2ObjectMap.Entry<IntSortedSet> entry = entries.next();\n          final IntSortedSet rowNums = entry.getValue();\n\n          if (rowNums.size() != 1) {\n            throw new ISE(\"Expected single element\");\n          }\n\n          indexAsArray[Ints.checkedCast(entry.getLongKey() - minLongKey)] = rowNums.firstInt();\n          entries.remove();\n        }\n\n        assert index.isEmpty();\n\n        // Early return of specialized implementation.\n        return new UniqueLongArrayIndex(indexAsArray, minLongKey);\n      }\n    }\n\n    return new MapIndex(keyType, index, nullIndex, nonNullKeysUnique);\n  }\n}\n","sourceCodeStart":141,"sourceCodeEnd":176,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/segment/join/table/RowBasedIndexBuilder.java#L141-L176","documentation":"During index building, after grouping rows by key value, each key is expected to map to exactly one row number for the compressed single-row index layout. A key mapping to multiple rows violates this invariant and RowBasedIndexBuilder.build throws IllegalStateException.","triggerScenarios":"Building an indexed table (RowBasedIndexedTable.build / index) where a key-column value occurs in more than one row, i.e. duplicate key rows in the input, when the builder's uniqueness expectation is violated in the long-key compaction path.","commonSituations":"Feeding non-unique data into a table declared with key columns (an implicit 'unique key' assumption); duplicate rows from repeated ingestion; building a lookup-style table from a denormalized dataset.","solutions":["Deduplicate the input rows on the key columns before building the table","Choose different key columns that uniquely identify each row","Aggregate the rows (e.g. group by key and pick first/max) prior to indexing","Verify upstream data for accidental duplicates"],"exampleFix":"// before\nrows.stream() // may contain duplicate keys\n    .collect(RowBasedIndexedTable.build(...));\n// after\nCollection<Row> deduped = rows.stream()\n    .collect(Collectors.toMap(r -> r.get(\"id\"), r -> r, (a, b) -> a))\n    .values();\nRowBasedIndexedTable.build(deduped, ...);","handlingStrategy":"validation","validationCode":"boolean keysUnique = rows.stream().map(r -> r.get(keyCol)).distinct().count() == rows.size();","typeGuard":null,"tryCatchPattern":"try { table = builder.build(rows, cacheKey); } catch (IllegalStateException e) { /* deduplicate rows and retry */ }","preventionTips":["Deduplicate on key columns before building the indexed table","Verify key uniqueness with a pre-pass over the data"],"tags":["java","index-build","duplicate-keys","illegal-state"],"backgroundTag":"internal-invariant-violation","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}