{"record":{"id":"856e62baf4533529","repo":"apache/cassandra","slug":"ranges-supplied-to-sstablecursorreader-must-be-non","errorCode":null,"errorMessage":"Ranges supplied to SSTableCursorReader must be non-overlapping and in ascending order.","messagePattern":"Ranges supplied to SSTableCursorReader must be non-overlapping and in ascending order\\.","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/io/sstable/SSTableCursorReader.java","lineNumber":679,"sourceCode":"     */\n    private int afterPartitionEnd()\n    {\n        return dataReader.getPosition() < segmentEnd ? PARTITION_START : advanceSegment();\n    }\n\n    /**\n     * Enters the next segment that has bytes, as {@code SSTableSimpleScanner.advanceRange} does,\n     * and leaves the reader at its first partition.\n     *\n     * @return PARTITION_START, or DONE when no segment is left\n     */\n    private int advanceSegment()\n    {\n        while (segmentIndex < segments.length)\n        {\n            PartitionPositionBounds next = segments[segmentIndex++];\n            if (segmentEnd > next.lowerPosition)\n                throw new IllegalArgumentException(\"Ranges supplied to SSTableCursorReader must be non-overlapping and in ascending order.\");\n            if (next.upperPosition < next.lowerPosition)\n                throw new IllegalArgumentException(\"A range supplied to SSTableCursorReader ends before it starts: \"\n                                                   + next.lowerPosition + \" > \" + next.upperPosition);\n            // An empty range carries no partition. Skip it WITHOUT touching segmentStart, segmentEnd\n            // or the byte accounting: bytesRead() is bytesReadInPreviousSegments plus the progress\n            // through the current segment, so moving those to a range the reader never visits makes\n            // the count go backwards. The scanner avoids this by seeking to the empty range's start;\n            // not seeking is cheaper and reads nothing outside a range this cursor covers.\n            if (next.lowerPosition == next.upperPosition)\n                continue;\n\n            bytesReadInPreviousSegments += segmentEnd - segmentStart;\n            segmentStart = next.lowerPosition;\n            segmentEnd = next.upperPosition;\n            try\n            {\n                seekPartition(segmentStart);\n            }","sourceCodeStart":661,"sourceCodeEnd":697,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/io/sstable/SSTableCursorReader.java#L661-L697","documentation":"SSTableCursorReader reads a sequence of byte-range segments from an sstable and requires them strictly ordered. advanceSegment() detects that the next range starts before the current segment's end (overlap or descending order) and throws IllegalArgumentException.","triggerScenarios":"Constructing SSTableCursorReader with a segments array where an earlier range's upperPosition exceeds a later range's lowerPosition.","commonSituations":"Custom tooling building PartitionPositionBounds lists without sorting; off-by-one errors when slicing an sstable for parallel scanning; merging ranges from multiple sources unsorted.","solutions":["Sort the ranges by lowerPosition before constructing the reader.","Coalesce overlapping/adjacent ranges before passing them in.","Pre-validate the segments array with a loop and fail fast with a clearer error.","Fix the producer of the bounds so it emits non-overlapping ascending ranges."],"exampleFix":"// before\nSSTableCursorReader reader = new SSTableCursorReader(..., segments);\n// after\nArrays.sort(segments, Comparator.comparingLong(PartitionPositionBounds::lowerPosition));\nfor (int i = 1; i < segments.length; i++)\n    if (segments[i-1].upperPosition > segments[i].lowerPosition)\n        throw new IllegalArgumentException(\"overlapping range at index \" + i);\nSSTableCursorReader reader = new SSTableCursorReader(..., segments);","handlingStrategy":"validation","validationCode":"for (int i = 1; i < segments.length; i++) {\n    if (segments[i].lowerPosition < segments[i-1].upperPosition)\n        throw new IllegalArgumentException(\"overlapping/out-of-order range at \" + i);\n    if (segments[i].upperPosition < segments[i].lowerPosition)\n        throw new IllegalArgumentException(\"inverted range at \" + i);\n}","typeGuard":null,"tryCatchPattern":"try {\n    reader = new SSTableCursorReader(..., segments);\n} catch (IllegalArgumentException e) {\n    // re-sort/coalesce ranges and retry once\n}","preventionTips":["Always sort ranges by lowerPosition before constructing the reader.","Coalesce overlapping ranges into merged bounds.","Unit-test range producers for empty, adjacent, and overlapping inputs."],"tags":["sstable","argument-validation","bounds"],"backgroundTag":"invalid-argument-value","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}