{"record":{"id":"3700a99ff33e2930","repo":"apache/druid","slug":"key-is-not-sortable","errorCode":null,"errorMessage":"Key is not sortable","messagePattern":"Key is not sortable","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/frame/processor/FrameChannelMerger.java","lineNumber":127,"sourceCode":"      @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;\n    this.frameWriterFactory = frameWriterFactory;\n    this.sortKey = sortKey;\n    this.partitions = partitionsToUse;\n    this.rowLimit = rowLimit;\n    this.currentFrames = new FramePlus[inputChannels.size()];\n    this.remainingChannels = new IntAVLTreeSet(IntSets.fromTo(0, inputChannels.size()));\n    this.tournamentTree = new TournamentTree(\n        inputChannels.size(),\n        (k1, k2) -> {\n          final FramePlus frame1 = currentFrames[k1];\n          final FramePlus frame2 = currentFrames[k2];\n\n          if (frame1 == frame2) {","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/frame/processor/FrameChannelMerger.java#L109-L145","documentation":"FrameChannelMerger requires that every KeyColumn in the supplied sortKey has a sortable KeyOrder (a defined ascending/descending order, not an unspecifiable one). It throws IAE if any column's order cannot be used for actual comparison-based sorting, since the merger physically orders rows.","triggerScenarios":"Constructing a FrameChannelMerger with a sortKey containing a KeyColumn whose KeyOrder is not sortable (e.g. KeyOrder.NONE or an order flag where 'unsorted' is meaningful), often when reusing clustering keys that were only meant for partitioning.","commonSituations":"Copying a ClusterBy sort key into a merge stage after a code change introduced a non-sortable order; deserializing keys from config/query JSON where the order field is unset or wrong.","solutions":["Set an explicit KeyOrder.ASCENDING or KeyOrder.DESCENDING on every KeyColumn in the sort key.","Check orders before constructing: sortKey.stream().allMatch(k -> k.order().sortable()), and fix any that fail.","If the key was deserialized, ensure the order field is populated with a valid sortable value."],"exampleFix":"// before\nnew KeyColumn(\"col\", KeyOrder.NONE);\n// after\nnew KeyColumn(\"col\", KeyOrder.ASCENDING);","handlingStrategy":"validation","validationCode":"for (KeyColumn kc : sortKey) {\n  if (!kc.order().sortable()) {\n    throw new IllegalArgumentException(\"Column '\" + kc.columnName() + \"' has a non-sortable order\");\n  }\n}","typeGuard":"boolean sortableKey(List<KeyColumn> key) { return key.stream().allMatch(k -> k.order().sortable()); }","tryCatchPattern":"try {\n  merger = new FrameChannelMerger(allocator, sortKey, channels, null, partitions, rowLimit);\n} catch (IllegalArgumentException e) {\n  // fix sort key orders to ASCENDING/DESCENDING\n}","preventionTips":["Always specify KeyOrder.ASCENDING or DESCENDING explicitly.","Never reuse partition-only keys as sort keys without setting orders.","Validate deserialized keys before use."],"tags":["java","argument-validation","sort-key"],"backgroundTag":"invalid-enum-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"}