{"record":{"id":"7c39acd4fadfb87e","repo":"apache/druid","slug":"pre-sorted-data-required-rows-s-and-s-were-n","errorCode":null,"errorMessage":"Pre-sorted data required, rows[%s] and [%s] were not in order","messagePattern":"Pre-sorted data required, rows\\[(.+?)\\] and \\[(.+?)\\] were not in order","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/rowsandcols/semantic/DefaultGroupPartitioner.java","lineNumber":64,"sourceCode":"    for (String column : columns) {\n      final Column theCol = rac.findColumn(column);\n      if (theCol == null) {\n        // The column doesn't exist.  In this case, we assume it's always the same value: null.  If it's always\n        // the same, then it doesn't impact grouping at all and can be entirely skipped.\n        continue;\n      }\n      final ColumnAccessor accessor = theCol.toAccessor();\n\n      int currGroup = 0;\n      int prevGroupVal = 0;\n      for (int i = 1; i < retVal.length; ++i) {\n        if (retVal[i] == prevGroupVal) {\n          int comparison = accessor.compareRows(i - 1, i);\n          if (comparison == 0) {\n            retVal[i] = currGroup;\n            continue;\n          } else if (comparison > 0) { // \"greater than\"\n            throw new ISE(\"Pre-sorted data required, rows[%s] and [%s] were not in order\", i - 1, i);\n          } // the 3rd condition (\"less than\") means create a new group, so let it fall through\n        }\n\n        // We have a new group, so walk things forward.\n        prevGroupVal = retVal[i];\n        retVal[i] = ++currGroup;\n      }\n    }\n\n    return retVal;\n  }\n}\n","sourceCodeStart":46,"sourceCodeEnd":77,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/rowsandcols/semantic/DefaultGroupPartitioner.java#L46-L77","documentation":"DefaultGroupPartitioner.computeGroupings assigns group IDs by scanning consecutive rows and requires rows pre-sorted by the grouping columns. If it detects two adjacent rows where the previous row compares greater than the current one (comparison > 0) while their group values are equal, the pre-sort invariant is violated and Druid throws ISE.","triggerScenarios":"computeGroupings is called on data whose accessor.compareRows(i-1, i) returns a positive value for adjacent rows, i.e. input rows are out of order relative to the grouping keys.","commonSituations":"A downstream/window operator consuming unsorted input; a sort stage removed or broken upstream; custom adapter feeding rows in arbitrary order.","solutions":["Ensure the input is sorted by the grouping columns before calling computeGroupings","Insert/repair an upstream sort stage","Verify the row accessor's comparison is consistent with the actual sort order used"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// verify adjacent rows are ordered before grouping\nfor (int i = 1; i < accessor.numRows(); i++) {\n  if (accessor.compareRows(i - 1, i) > 0) {\n    throw new IllegalStateException(\"rows not pre-sorted at \" + i);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  groupings = partitioner.computeGroupings(accessor);\n} catch (IllegalStateException e) {\n  if (e.getMessage().contains(\"Pre-sorted data required\")) {\n    accessor = sort(accessor);\n    groupings = partitioner.computeGroupings(accessor);\n  } else throw e;\n}","preventionTips":["Always sort input by grouping columns before group-partitioning","Keep the sort operator's ordering consistent with the accessor's comparator"],"tags":["grouping","presorted-input","invariant-violation"],"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-17T15:17:12.973Z"}