{"id":"656ed8042f7eb094","repo":"go-redis/redis","slug":"ft-aggregate-collect-must-follow-a-groupby-step","errorCode":null,"errorMessage":"FT.AGGREGATE: Collect must follow a GroupBy step","messagePattern":"FT\\.AGGREGATE: Collect must follow a GroupBy step","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"search_builders.go","lineNumber":328,"sourceCode":"\t}\n\tg := b.options.Steps[n-1].GroupBy\n\tg.Reduce = append(g.Reduce, FTAggregateReducer{Reducer: fn, Args: args, As: alias})\n\treturn b\n}\n\n// Collect adds a REDUCE COLLECT clause to the last step, which must be a\n// GROUPBY. The COLLECT options (FIELDS/DISTINCT/SORTBY/LIMIT/AS) are rendered\n// and the argument count is computed automatically; field and sort names are\n// normalized to a single \"@\" prefix. Set FTAggregateCollect.As to alias the\n// output column.\n//\n// If the last step is not a GROUPBY, or the options are invalid (no FIELDS\n// selector), Run returns the recorded error without issuing the command.\n// COLLECT requires Redis 8.8+ with unstable features enabled.\nfunc (b *AggregateBuilder) Collect(o FTAggregateCollect) *AggregateBuilder {\n\tn := len(b.options.Steps)\n\tif n == 0 || b.options.Steps[n-1].GroupBy == nil {\n\t\tb.setErr(fmt.Errorf(\"FT.AGGREGATE: Collect must follow a GroupBy step\"))\n\t\treturn b\n\t}\n\treducer, err := NewCollectReducer(o)\n\tif err != nil {\n\t\tb.setErr(err)\n\t\treturn b\n\t}\n\tg := b.options.Steps[n-1].GroupBy\n\tg.Reduce = append(g.Reduce, reducer)\n\treturn b\n}\n\n// SortBy adds SORTBY <field> ASC|DESC. Consecutive SortBy calls (with no\n// other step in between) are merged into a single SORTBY clause so fields\n// act as tiebreakers. A SortBy call after a non-SortBy step starts a new\n// SORTBY step.\n//\n// Note: this is a semantics change from earlier experimental versions of","sourceCodeStart":310,"sourceCodeEnd":346,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/search_builders.go#L310-L346","documentation":"Recorded (and later returned by Run) when AggregateBuilder.Collect is called but the most recent pipeline step is not a GROUPBY. Collect builds a REDUCE COLLECT clause that attaches to a GROUPBY step; like Reduce/ReduceAs, misordering defers the error to Run.","triggerScenarios":"Calling b.Collect(o) when the last step is not a GroupBy, or the builder has no steps. COLLECT also requires Redis 8.8+ with unstable features; the ordering check fires before any version validation.","commonSituations":"Reordered pipeline where Collect was placed before GroupBy; using Collect as if it were a top-level step rather than a REDUCE clause; refactoring that moved Collect above its GroupBy.","solutions":["Call b.GroupBy(...) immediately before b.Collect(o) so COLLECT attaches to a GROUPBY step.","Remember COLLECT is a reducer, not a standalone step; pair it with GroupBy every time.","Add a test that builds the pipeline and asserts Run does not return the ordering error."],"exampleFix":"// before\nb := c.NewAggregateBuilder(ctx, idx, q)\nb.Collect(redis.FTAggregateCollect{Fields: []string{\"@sku\"}}) // no GroupBy\n\n// after\nb := c.NewAggregateBuilder(ctx, idx, q)\nb.GroupBy(\"@order\").Collect(redis.FTAggregateCollect{Fields: []string{\"@sku\"}})","handlingStrategy":"validation","validationCode":"// COLLECT is a REDUCE clause; ensure a GroupBy precedes it.\nif len(steps) == 0 || steps[len(steps)-1].GroupBy == nil {\n    return errors.New(\"Collect requires a preceding GroupBy\")\n}","typeGuard":null,"tryCatchPattern":"res, err := b.Run()\nif err != nil && strings.Contains(err.Error(), \"Collect must follow a GroupBy step\") {\n    // add a GroupBy before Collect\n}","preventionTips":["Always pair Collect with a GroupBy in the same fluent chain.","Remember COLLECT requires Redis 8.8+ with unstable features; document this at the call site.","Unit-test the builder to catch ordering and version-gating early."],"tags":["ftaggregate","search","collect","builder","validation","api-misuse"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}