{"record":{"id":"adb019c028f74f29","repo":"apache/druid","slug":"cannot-change-output-name-for-aggregatorfactory-s","errorCode":null,"errorMessage":"Cannot change output name for AggregatorFactory[%s].","messagePattern":"Cannot change output name for AggregatorFactory\\[(.+?)\\]\\.","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/aggregation/AggregatorFactory.java","lineNumber":367,"sourceCode":"  {\n    return this;\n  }\n\n  /**\n   * Used in cases where we want to change the output name of the aggregator to something else. For eg: if we have\n   * a query `select a, sum(b) as total group by a from table` the aggregator returned from the native group by query is \"a0\" set in\n   * {@link org.apache.druid.sql.calcite.rel.DruidQuery#computeAggregations}. We can use withName(\"total\") to set the output name\n   * of the aggregator to \"total\".\n   * <p>\n   * As all implementations of this interface method may not exist, callers of this method are advised to handle such a case.\n   *\n   * @param newName newName of the output for aggregator factory\n   * @return AggregatorFactory with the output name set as the input param.\n   */\n  @SuppressWarnings(\"unused\")\n  public AggregatorFactory withName(String newName)\n  {\n    throw new UOE(\"Cannot change output name for AggregatorFactory[%s].\", this.getClass().getName());\n  }\n\n  /**\n   * Check to see if we can make a 'combining' factory of this aggregator that is suitable to process input from a\n   * selector of values produced by the other {@link AggregatorFactory} representing pre-aggregated data. Typically,\n   * this means that this and the other aggregator have the same inputs ({@link #requiredFields()}, and the same\n   * options for how the data was constructed into the intermediary type. If suitable, this method returns a\n   * 'combining' aggregator factory of this aggregator to use to process the pre-aggregated data which was produced by\n   * the other aggregator.\n   * <p>\n   * This method is used indirectly in service of checking if a\n   * {@link org.apache.druid.segment.projections.QueryableProjection} can be used instead of the base table during\n   * {@link org.apache.druid.segment.CursorFactory#makeCursorHolder(CursorBuildSpec)}, which checks if this\n   * aggregator can be substituted for its combining aggregator if and only if there exists a column that a cursor can\n   * read which was created by an aggregator that satisfies this method. In other words, this aggregator is the 'query'\n   * aggregator defined on the {@link CursorBuildSpec}, the argument to this method is the aggregator which created\n   * some column whose selectors are available to the cursor. If all aggregators on the {@link CursorBuildSpec} can be\n   * paired with aggregators from the underlying table in the cursor factory, then","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/aggregation/AggregatorFactory.java#L349-L385","documentation":"AggregatorFactory.withName(newName) is a no-op hook in the base class that throws UOE; only aggregator factories that support renaming override it. Calling it on an unsupported factory means the requested output-name substitution cannot be performed.","triggerScenarios":"Calling withName() on a concrete AggregatorFactory that does not override it; callers include testWithName, substituteCombiningFactory, and getCombiningFactory paths during query planning/rewriting.","commonSituations":"Custom aggregators lacking a withName override used in queries that require renaming (e.g. nested groupBy or post-aggregation rewriting); older aggregator implementations not updated for the renaming API.","solutions":["Override withName(String) in your AggregatorFactory subclass to return a copy with the new output name","Use an aggregator implementation that supports withName","Restructure the query to avoid renaming this aggregator (e.g. alias at a different layer)"],"exampleFix":"// before\n// base class behavior\nthrow new UOE(\"Cannot change output name for AggregatorFactory[%s].\", getClass().getName());\n// after\n@Override\npublic AggregatorFactory withName(String newName) {\n  return new DoubleSumAggregatorFactory(newName, fieldName, expression);\n}","handlingStrategy":"type-guard","validationCode":"// before renaming\nMethod m = factory.getClass().getMethod(\"withName\", String.class);\nif (m.getDeclaringClass().equals(AggregatorFactory.class)) {\n  throw new IllegalStateException(\"Factory does not support withName: \" + factory.getClass());\n}","typeGuard":"boolean supportsRename(AggregatorFactory f) {\n  try { f.withName(\"probe\"); return true; } catch (UnsupportedOperationException e) { return false; }\n}","tryCatchPattern":"try {\n  renamed = factory.withName(newName);\n} catch (UnsupportedOperationException e) {\n  log.error(\"withName unsupported for %s\", factory.getClass(), e);\n  throw e;\n}","preventionTips":["Override withName in custom aggregator factories","Test custom aggregators in nested groupBy / renaming scenarios","Avoid relying on output-name rewriting for legacy aggregator types"],"tags":["aggregation","druid","unsupported-operation"],"backgroundTag":"method-not-implemented","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"}