apache/druid · error · IllegalArgumentException
Cannot force limit push down when a having spec is present.
Error message
Cannot force limit push down when a having spec is present.
What it means
GroupByQuery.validateAndGetForceLimitPushDown rejects forcing limit push down when the query has a havingSpec. Limit push down moves the limit/sort into the group-by engine itself, which cannot coexist with a HAVING filter, so the query throws IAE rather than silently producing wrong results.
Solutions
- Remove the havingSpec from the query, or drop the force-limit-push-down context flag
- Apply the HAVING filter in an outer wrapper query and keep limit push down on the inner group-by
- If using SQL, rewrite the query so HAVING is applied by a separate aggregation level
Example fix
// before
GroupByQuery q = orig.withOverriddenContext(ImmutableMap.of("applyLimitPushDown", true)); // has havingSpec
// after
GroupByQuery q = orig.withOverriddenContext(ImmutableMap.of("applyLimitPushDown", false)); // or remove havingSpec Defensive patterns
Strategy: validation
Validate before calling
if (query.getHavingSpec() != null && Boolean.TRUE.equals(query.getContextValue("applyLimitPushDown"))) { throw new IllegalArgumentException("Disable limit push down when a havingSpec is present"); } Type guard
boolean canForceLimitPushDown(GroupByQuery q) { return q.getHavingSpec() == null; } Try / catch
try { return isApplyLimitPushDown(query, forcePushDown); } catch (IllegalArgumentException e) { log.warn(e, "Falling back to no limit push down"); return false; } Prevention
- Check havingSpec before enabling applyLimitPushDown context
- Encapsulate limit-push-down enabling in a helper that validates query features
When it happens
Trigger: Setting context key 'applyLimitPushDown' (or useCache/broker forcing) to true on a GroupByQuery whose Builder.setHavingSpec(...) was called; isApplyLimitPushDown then calls validateAndGetForceLimitPushDown and throws.
Common situations: Users enable limit push down for performance while also using SQL HAVING clauses or havingSpecs configured via the native API; SQL planner-generated queries with HAVING translated to a havingSpec.
Related errors
- Ambiguous build, limit
- Ambiguous build, limitSpec
- buffer for list is too small, was
- Duplicate output name
- Expected [1] child, got
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/20f9c48869928264.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/query/groupby/GroupByQuery.java:514
}
}
);
}
private boolean validateAndGetForceLimitPushDown()
{
final boolean forcePushDown = context().getBoolean(GroupByQueryConfig.CTX_KEY_FORCE_LIMIT_PUSH_DOWN, false);
if (forcePushDown) {
if (!(limitSpec instanceof DefaultLimitSpec)) {
throw new IAE("When forcing limit push down, a limit spec must be provided.");
}
if (!((DefaultLimitSpec) limitSpec).isLimited()) {
throw new IAE("When forcing limit push down, the provided limit spec must have a limit.");
}
if (havingSpec != null) {
throw new IAE("Cannot force limit push down when a having spec is present.");
}
for (OrderByColumnSpec orderBySpec : ((DefaultLimitSpec) limitSpec).getColumns()) {
if (OrderByColumnSpec.getPostAggIndexForOrderBy(orderBySpec, postAggregatorSpecs) > -1) {
throw new UnsupportedOperationException("Limit push down when sorting by a post aggregator is not supported.");
}
}
}
return forcePushDown;
}
private RowSignature computeResultRowSignature(final RowSignature.Finalization finalization)
{
final RowSignature.Builder builder = RowSignature.builder();
if (universalTimestamp == null) {
builder.addTimeColumn();
}View on GitHub (pinned to 9b90983fd2)