{"record":{"id":"15a386ebf1dd3fdd","repo":"apache/druid","slug":"row-d-out-of-bounds","errorCode":null,"errorMessage":"Row [%d] out of bounds","messagePattern":"Row \\[(.+?)\\] out of bounds","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"multi-stage-query/src/main/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessor.java","lineNumber":837,"sourceCode":"    public void clear()\n    {\n      holders.clear();\n      markFrame = -1;\n      markRow = -1;\n      currentFrame = -1;\n    }\n\n    /**\n     * Whether the provided frame and row compares equally to the mark row. The provided row must be at, or after,\n     * the mark row.\n     */\n    private boolean isSameKeyAsMark(final FrameHolder holder, final int row)\n    {\n      if (markFrame < 0) {\n        throw new ISE(\"No marked frame\");\n      }\n      if (row < 0 || row >= holder.frame.numRows()) {\n        throw new ISE(\"Row [%d] out of bounds\", row);\n      }\n\n      final FrameHolder markHolder = holders.get(markFrame);\n      final int cmp = markHolder.comparisonWidget.compare(markRow, holder.comparisonWidget, row);\n\n      if (cmp > 0) {\n        // The provided row is at, or after, the marked row.\n        // Therefore, cmp > 0 may indicate that input was provided out of order.\n        throw new ISE(\"Row compares higher than mark; out-of-order input?\");\n      }\n\n      return cmp == 0;\n    }\n  }\n\n  /**\n   * Selector for joined rows. This is used as an input to {@link #frameWriter}.\n   */","sourceCodeStart":819,"sourceCodeEnd":855,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessor.java#L819-L855","documentation":"isSameKeyAsMark validates that the row being compared lies inside the frame's row range. If markFrame is unset or the row index is negative or >= holder.frame.numRows(), an IllegalStateException('Row [%d] out of bounds') is thrown, indicating the merge-join logic attempted to compare a row that does not exist — an internal invariant breach rather than a data or user error.","triggerScenarios":"Internal: during hasCompleteSetForMark or isCurrentSameKeyAsMark, the processor passes a row index outside the current frame holder (cursor bookkeeping desync, e.g. after a frame flush or boundary miscount).","commonSituations":"Rarely user-caused; surfaces as an engine bug in sort-merge join cursor management, potentially reproducible with particular frame-boundary sizes and join key orderings; report to Druid with the query.","solutions":["Retry the query; if reproducible, file a Druid bug including the query and stack trace","Adjust frame/row batch sizing context parameters as a workaround to shift frame boundaries","Reduce join input sizes (filter, pre-aggregate) to avoid the failing boundary condition","Check for a newer Druid version with fixes to SortMergeJoinFrameProcessor"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  runMsqQuery(query);\n} catch (IllegalStateException | MSQException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"out of bounds\")) {\n    // engine bug: retry once, then collect query/trace and report to Druid\n  }\n}","preventionTips":["Keep Druid current; this class of bug is fixed in engine releases","Avoid custom stage extensions that mutate frame bookkeeping","Capture full stack traces for bug reports"],"tags":["msq","join","internal-error","invariant"],"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-14T05:17:10.506Z"}