{"record":{"id":"6ade58f9c7b0de29","repo":"apache/druid","slug":"skip-must-be-greater-than-zero-6ade58","errorCode":null,"errorMessage":"'skip' must be greater than zero","messagePattern":"'skip' must be greater than zero","errorType":"validation","errorClass":"IllegalArgumentException (IAE)","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/scan/ScanQueryOffsetSequence.java","lineNumber":48,"sourceCode":"import java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * A Sequence that wraps the results of a ScanQuery and skips a given number of rows. It is used to implement\n * the \"offset\" feature.\n */\npublic class ScanQueryOffsetSequence extends YieldingSequenceBase<ScanResultValue>\n{\n  private final Sequence<ScanResultValue> baseSequence;\n  private final long skip;\n\n  public ScanQueryOffsetSequence(Sequence<ScanResultValue> baseSequence, long skip)\n  {\n    this.baseSequence = baseSequence;\n    this.skip = skip;\n\n    if (skip < 1) {\n      throw new IAE(\"'skip' must be greater than zero\");\n    }\n  }\n\n  @Override\n  public <OutType> Yielder<OutType> toYielder(\n      final OutType initValue,\n      final YieldingAccumulator<OutType, ScanResultValue> accumulator\n  )\n  {\n    final SkippingYieldingAccumulator<OutType> skippingAccumulator = new SkippingYieldingAccumulator<>(accumulator);\n    return wrapYielder(baseSequence.toYielder(initValue, skippingAccumulator), skippingAccumulator);\n  }\n\n  private <OutType> Yielder<OutType> wrapYielder(\n      final Yielder<OutType> yielder,\n      final SkippingYieldingAccumulator<OutType> accumulator\n  )\n  {","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/scan/ScanQueryOffsetSequence.java#L30-L66","documentation":"ScanQueryOffsetSequence wraps a base scan sequence to skip the first N result rows (query offset). A skip value below 1 is meaningless (offset must be at least 1 to have an effect), so the constructor throws IAE immediately.","triggerScenarios":"Constructing ScanQueryOffsetSequence with skip = 0 or negative, typically from a ScanQuery whose getScanRowsOffset() is 0 or negative (e.g. context 'offset':0 applied via mergeResults).","commonSituations":"Programmatic query building where offset defaults to 0 and the offset sequence is applied unconditionally; manual API misuse; clients sending negative offset values in query context.","solutions":["Only wrap with ScanQueryOffsetSequence when query.getScanRowsOffset() >= 1","Fix client-supplied offset values to be at least 1 (or omit offset)","Guard construction: if (offset > 0) apply offset sequence, else use base sequence"],"exampleFix":"// before\nSequence<ScanResultValue> seq = new ScanQueryOffsetSequence(base, query.getScanRowsOffset()); // offset may be 0\n// after\nfinal long offset = query.getScanRowsOffset();\nSequence<ScanResultValue> seq = offset >= 1 ? new ScanQueryOffsetSequence(base, offset) : base;","handlingStrategy":"validation","validationCode":"long offset = query.getScanRowsOffset();\nif (offset < 1) { /* do not construct ScanQueryOffsetSequence */ }","typeGuard":"boolean offsetApplicable(ScanQuery q) { return q.getScanRowsOffset() >= 1; }","tryCatchPattern":"try {\n  seq = new ScanQueryOffsetSequence(base, skip);\n} catch (IllegalArgumentException e) {\n  seq = base; // skip<=0 means no offset needed\n}","preventionTips":["Only apply offset sequencing when offset > 0","Sanitize client-provided offset values to be positive integers","Document that offset starts at 1 when used"],"tags":["druid","scan-query","argument-validation","pagination"],"backgroundTag":"invalid-argument-value","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"}