{"record":{"id":"26c1ce3cfb5b92f0","repo":"apache/druid","slug":"must-use-filter-or-always-havingspec","errorCode":null,"errorMessage":"Must use 'filter' or 'always' havingSpec","messagePattern":"Must use 'filter' or 'always' havingSpec","errorType":"validation","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"multi-stage-query/src/main/java/org/apache/druid/msq/querykit/groupby/GroupByPostShuffleFrameProcessor.java","lineNumber":343,"sourceCode":"      return row -> {};\n    }\n  }\n\n  @Nullable\n  private static HavingSpec cloneHavingSpec(final GroupByQuery query)\n  {\n    if (query.getHavingSpec() == null || query.getHavingSpec() instanceof AlwaysHavingSpec) {\n      return null;\n    } else if (query.getHavingSpec() instanceof DimFilterHavingSpec) {\n      final DimFilterHavingSpec dimFilterHavingSpec = (DimFilterHavingSpec) query.getHavingSpec();\n      final DimFilterHavingSpec clonedHavingSpec = new DimFilterHavingSpec(\n          dimFilterHavingSpec.getDimFilter(),\n          dimFilterHavingSpec.isFinalize()\n      );\n      clonedHavingSpec.setQuery(query);\n      return clonedHavingSpec;\n    } else {\n      throw new UnsupportedOperationException(\"Must use 'filter' or 'always' havingSpec\");\n    }\n  }\n\n  /**\n   * Create virtual columns containing \"bonus\" fields that should be attached to the {@link FrameWriter} for\n   * this processor. Kept in sync with the signature generated by {@link GroupByQueryKit}.\n   */\n  private static VirtualColumns makeVirtualColumnsForFrameWriter(\n      @Nullable final VirtualColumn partitionBoostVirtualColumn,\n      final ObjectMapper jsonMapper,\n      final GroupByQuery query\n  )\n  {\n    List<VirtualColumn> virtualColumns = new ArrayList<>();\n\n    virtualColumns.add(partitionBoostVirtualColumn);\n    final VirtualColumn segmentGranularityVirtualColumn =\n        QueryKitUtils.makeSegmentGranularityVirtualColumn(jsonMapper, query.context());","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/groupby/GroupByPostShuffleFrameProcessor.java#L325-L361","documentation":"GroupByPostShuffleFrameProcessor.cloneHavingSpec can only clone HavingSpec types it supports (DimFilterHavingSpec and AlwaysHavingSpec), so the cloned having spec can be attached to the derived query. Any other HavingSpec implementation triggers this UnsupportedOperationException because the processor cannot rewrite it correctly for the post-shuffle query.","triggerScenarios":"Running a group-by query through MSQ post-shuffle processing where the query's havingSpec is neither a DimFilterHavingSpec nor an AlwaysHavingSpec (e.g. a legacy ComparatorHavingSpec, or a custom HavingSpec).","commonSituations":"Older/native queries using comparator-based having specs (e.g. 'greaterThan' numeric having) submitted through SQL/MSQ; custom HavingSpec plugins from extensions; queries built by older tooling.","solutions":["Replace the having spec with a DimFilterHavingSpec (a filter-based HAVING clause, e.g. HAVING SUM(x) > 10 in SQL).","In SQL, express the HAVING condition as a standard filter so it compiles to DimFilterHavingSpec.","Move the having logic into an outer query (subquery aggregation, then filter outside).","Remove the havingSpec and filter results client-side if unsupported."],"exampleFix":"// before (native groupBy)\n\"havingSpec\": {\"type\": \"greaterThan\", \"aggregation\": \"agg\", \"value\": 10}\n// after\n\"havingSpec\": {\"type\": \"filter\", \"dimFilter\": {\"type\": \"expression\", \"expression\": \"\\\"agg\\\" > 10\"}}","handlingStrategy":"validation","validationCode":"// Check the havingSpec type before submitting a group-by through MSQ\nif (q.havingSpec && !['filter', 'always'].includes(q.havingSpec.type)) {\n  throw new Error('MSQ group-by supports only filter/always havingSpec; got: ' + q.havingSpec.type);\n}","typeGuard":"const hasSupportedHaving = (q) => !q.havingSpec || q.havingSpec.type === 'filter' || q.havingSpec.type === 'always';","tryCatchPattern":null,"preventionTips":["Use filter-based (DimFilterHavingSpec) having specs exclusively.","Express HAVING conditions as standard SQL filters.","Avoid legacy comparator having specs and custom HavingSpec implementations in MSQ queries."],"tags":["msq","groupby","having"],"backgroundTag":"unsupported-operation","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"}