Skip to main content

HAVING Filtering

having: arguments delegate entirely to HotChocolate.Data's filter convention. The grouping side wires HAVING by runtime type and HotChocolate.Data resolves the matching *OperationFilterInputType: operator renames, custom operators, and per-runtime-type bindings on the filter side flow through to HAVING with no grouping-side glue.

HAVING needs AddFiltering

AddGrouping(...) does not force AddFiltering(...) on consumers. When AddFiltering isn't registered, having: arguments are silently omitted and grouping still works for key / aggregate.

services
    .AddGraphQL()
    .AddFiltering()   // required for `having:` to surface
    .AddGrouping()
    .AddQueryType<Query>();

Custom-scalar HAVING

Register the filter input on HotChocolate.Data's side:

services
    .AddGraphQL()
    .AddFiltering(f => f
        .AddDefaults()
        .BindRuntimeType<Money, MoneyOperationFilterInputType>())   // filter side
    .AddGrouping(d => d
        .BindRuntimeType<Money, MoneyAggregateResultType>())        // grouping side
    .AddQueryType<Query>();

Operator names, custom operators, and per-type overlays go through AddFiltering. See the Operation_Rename and CustomOperator_DivisibleBy tests for the full pipeline.

Enabling OR within a HAVING clause

HotChocolate.Data ships numeric/comparable filter inputs (IntOperationFilterInput, LongOperationFilterInput, BooleanOperationFilterInput, etc.) with and/or disabled by default. StringOperationFilterInput ships with them enabled.

Convention-level f.AllowOr() is overridden by the per-type Configure, and f.Configure<TFilter>(d => d.AllowOr()) silently no-ops (the UseAnd/UseOr scalars get dropped by DataTypeExtensionHelper). The reliable path is a TypeInterceptor that flips the flags as each filter input completes:

using HotChocolate.Configuration;
using HotChocolate.Data.Filters;
using HotChocolate.Types.Descriptors.Configurations;
 
public sealed class EnableAndOrOnFilterInputs : TypeInterceptor
{
    public override void OnBeforeCompleteType(
        ITypeCompletionContext completionContext,
        TypeSystemConfiguration configuration)
    {
        if (configuration is FilterInputTypeConfiguration filter)
        {
            filter.UseAnd = true;
            filter.UseOr = true;
        }
    }
}
 
services
    .AddGraphQL()
    .AddFiltering()
    .AddGrouping()
    .TryAddTypeInterceptor<EnableAndOrOnFilterInputs>()
    .AddQueryType<Query>();

This affects regular [UseFiltering] surfaces too, usually what you want.

{
  employeeGrouping {
    count(having: { or: [{ eq: 3 }, { gt: 10 }] })
  }
}

OR composition across different aggregate operations is a separate limitation. See OR across different aggregates.