{"record":{"id":"2660d3e62df4893f","repo":"apache/druid","slug":"must-have-at-least-one-input-channel","errorCode":null,"errorMessage":"Must have at least one input channel","messagePattern":"Must have at least one input channel","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/frame/processor/FrameChannelMerger.java","lineNumber":114,"sourceCode":"   * @param sortKey            sort key for input and output frames\n   * @param combiner           optional combiner for merging rows with identical sort keys\n   * @param partitions         partitions for output frames. If non-null, output frames are written with\n   *                           partition numbers set according to this parameter\n   * @param rowLimit           maximum number of rows to write to the output channel\n   */\n  public FrameChannelMerger(\n      final List<ReadableFrameChannel> inputChannels,\n      final FrameReader frameReader,\n      final WritableFrameChannel outputChannel,\n      final FrameWriterFactory frameWriterFactory,\n      final List<KeyColumn> sortKey,\n      @Nullable final FrameCombiner combiner,\n      @Nullable final ClusterByPartitions partitions,\n      final long rowLimit\n  )\n  {\n    if (inputChannels.isEmpty()) {\n      throw new IAE(\"Must have at least one input channel\");\n    }\n\n    final ClusterByPartitions partitionsToUse =\n        partitions == null ? ClusterByPartitions.oneUniversalPartition() : partitions;\n\n    if (!partitionsToUse.allAbutting()) {\n      // To simplify merging logic, when frames we only look at the earliest and latest key in \"partitions\". To ensure\n      // correctness, we need to verify that there are no gaps.\n      throw new IAE(\"Partitions must all abut each other\");\n    }\n\n    if (!sortKey.stream().allMatch(keyColumn -> keyColumn.order().sortable())) {\n      throw new IAE(\"Key is not sortable\");\n    }\n\n    this.inputChannels = inputChannels;\n    this.outputChannel = outputChannel;\n    this.frameReader = frameReader;","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/frame/processor/FrameChannelMerger.java#L96-L132","documentation":"FrameChannelMerger merges multiple sorted Frame input channels into one sorted output. Merging is meaningless with zero inputs, so the constructor throws IAE when the inputChannels collection is empty. This is an upfront contract check: callers must supply at least one ReadableFrameChannel.","triggerScenarios":"Calling `new FrameChannelMerger(allocator, sortKey, List.of(), combiner, partitions, rowLimit)` or passing a channel list that ended up empty after filtering/partitioning produced no partitions.","commonSituations":"Query-stage code that builds merger inputs from a partition bound set which is empty; filtering channels before merge removed all of them; bugs where a stage receives no work items and still attempts to construct a merger.","solutions":["Ensure at least one input channel is provided; if a stage may legitimately have zero inputs, skip the merge rather than constructing a merger.","Guard the call: only create FrameChannelMerger when inputChannels.size() >= 1.","Trace why upstream partitioning produced zero channels (empty partition bound set) and fix the producer."],"exampleFix":"// before\nFrameChannelMerger merger = new FrameChannelMerger(allocator, sortKey, channels, null, partitions, rowLimit);\n// after\nif (channels.isEmpty()) {\n  return; // nothing to merge\n}\nFrameChannelMerger merger = new FrameChannelMerger(allocator, sortKey, channels, null, partitions, rowLimit);","handlingStrategy":"type-guard","validationCode":"if (inputChannels == null || inputChannels.isEmpty()) {\n  throw new IllegalStateException(\"Cannot merge: no input channels\");\n}","typeGuard":"boolean canMerge(List<ReadableFrameChannel> channels) { return channels != null && !channels.isEmpty(); }","tryCatchPattern":"try {\n  merger = new FrameChannelMerger(allocator, sortKey, channels, null, partitions, rowLimit);\n} catch (IllegalArgumentException e) {\n  // zero inputs: skip merge or fail the stage with context\n}","preventionTips":["Check channel count before constructing a merger.","Skip merge steps for stages with zero work items.","Log why partitioning produced zero channels."],"tags":["java","argument-validation","frame-merger"],"backgroundTag":"missing-required-argument","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"}