go-redis/redis · error

FT.AGGREGATE: LOADALL and LOAD are mutually exclusive

Error message

FT.AGGREGATE: LOADALL and LOAD are mutually exclusive

What it means

Returned by validateFTAggregateOptions when LoadAll is true and any individual step in Steps contains a Load field. LOADALL emits a single LOAD * on the wire, which is semantically incompatible with per-step LOAD clauses; the builder rejects the combination up front.

Source

Thrown at search_commands.go:696

func (c cmdable) FTAggregate(ctx context.Context, index string, query string) *MapStringInterfaceCmd {
	args := []interface{}{"FT.AGGREGATE", index, query}
	cmd := NewMapStringInterfaceCmd(ctx, args...)
	_ = c(ctx, cmd)
	return cmd
}

// validateFTAggregateOptions validates mutually exclusive combinations of
// FTAggregateOptions fields before any command arguments are constructed.
func validateFTAggregateOptions(options *FTAggregateOptions) error {
	if len(options.Steps) > 0 {
		if options.Load != nil || options.Apply != nil || options.GroupBy != nil ||
			options.SortBy != nil || options.SortByMax != 0 {
			return fmt.Errorf("FT.AGGREGATE: Steps cannot be combined with the deprecated Load, Apply, GroupBy, SortBy and SortByMax fields")
		}
		if options.LoadAll {
			for _, step := range options.Steps {
				if step.Load != nil {
					return fmt.Errorf("FT.AGGREGATE: LOADALL and LOAD are mutually exclusive")
				}
			}
		}
	}
	if options.LoadAll && options.Load != nil {
		return fmt.Errorf("FT.AGGREGATE: LOADALL and LOAD are mutually exclusive")
	}
	return nil
}

// appendFTAggregateStep appends the Redis command arguments for a single
// aggregation pipeline step. Each step must set exactly one of Load, Apply,
// GroupBy or SortBy.
func appendFTAggregateStep(args []interface{}, step FTAggregateStep) ([]interface{}, error) {
	set := 0
	if step.Load != nil {
		set++
	}

View on GitHub (pinned to 36d97525cd)

Solutions

  1. Drop LoadAll and express all LOADs as step-level Load entries, or keep LoadAll and remove every step-level Load.
  2. Audit the Steps slice to confirm no FTAggregateStep.Load is set when LoadAll is true.
  3. Prefer LoadAll only when you genuinely want every field and have no per-step LOAD requirements.

Example fix

// before
opts := &FTAggregateOptions{
    LoadAll: true,
    Steps:   []FTAggregateStep{{Load: &FTAggregateLoad{Field: "price"}}},
}
// after (option A: keep LoadAll)
opts := &FTAggregateOptions{LoadAll: true, Steps: []FTAggregateStep{{Apply: ...}}}
// after (option B: explicit step LOADs)
opts := &FTAggregateOptions{Steps: []FTAggregateStep{{Load: &FTAggregateLoad{Field: "price"}}}}
Defensive patterns

Strategy: validation

Validate before calling

func validateLoadAllVsStepLoads(opts *FTAggregateOptions) error {
	if !opts.LoadAll { return nil }
	for i, st := range opts.Steps {
		if st.Load != nil {
			return fmt.Errorf("Steps[%d] has Load while LoadAll is set", i)
		}
	}
	return nil
}

Prevention

When it happens

Trigger: Setting FTAggregateOptions{LoadAll: true, Steps: []FTAggregateStep{{Load: &FTAggregateLoad{Field: "price"}}}}. Triggered during option validation before serialization.

Common situations: Enabling LoadAll for convenience while still hand-specifying a step-level LOAD. Migrating LOAD clauses into Steps without clearing the LoadAll flag. Copy-pasting options that already had LoadAll enabled.

Related errors


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /data/errors/f6c29d502bb71c14.json. Report an issue: GitHub.