{"id":"8f624a9b78b6563d","repo":"go-redis/redis","slug":"ft-aggregate-sortbymax-must-follow-a-sortby-step","errorCode":null,"errorMessage":"FT.AGGREGATE: SortByMax must follow a SortBy step","messagePattern":"FT\\.AGGREGATE: SortByMax must follow a SortBy step","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"search_builders.go","lineNumber":366,"sourceCode":"// regardless of position in the pipeline.\nfunc (b *AggregateBuilder) SortBy(field string, asc bool) *AggregateBuilder {\n\tsb := FTAggregateSortBy{FieldName: field, Asc: asc, Desc: !asc}\n\tif n := len(b.options.Steps); n > 0 && b.options.Steps[n-1].SortBy != nil {\n\t\tb.options.Steps[n-1].SortBy.Fields = append(b.options.Steps[n-1].SortBy.Fields, sb)\n\t\treturn b\n\t}\n\tb.options.Steps = append(b.options.Steps, FTAggregateStep{\n\t\tSortBy: &FTAggregateSortByStep{Fields: []FTAggregateSortBy{sb}},\n\t})\n\treturn b\n}\n\n// SortByMax sets MAX <n> on the last SORTBY step. The last step must be a\n// SORTBY; otherwise Run will return an error.\nfunc (b *AggregateBuilder) SortByMax(max int) *AggregateBuilder {\n\tn := len(b.options.Steps)\n\tif n == 0 || b.options.Steps[n-1].SortBy == nil {\n\t\tb.setErr(fmt.Errorf(\"FT.AGGREGATE: SortByMax must follow a SortBy step\"))\n\t\treturn b\n\t}\n\tb.options.Steps[n-1].SortBy.Max = max\n\treturn b\n}\n\n// Filter sets FILTER <expr>.\nfunc (b *AggregateBuilder) Filter(expr string) *AggregateBuilder {\n\tb.options.Filter = expr\n\treturn b\n}\n\n// WithCursor enables WITHCURSOR [COUNT <n>] [MAXIDLE <ms>].\nfunc (b *AggregateBuilder) WithCursor(count, maxIdle int) *AggregateBuilder {\n\tb.options.WithCursor = true\n\tif b.options.WithCursorOptions == nil {\n\t\tb.options.WithCursorOptions = &FTAggregateWithCursor{}\n\t}","sourceCodeStart":348,"sourceCodeEnd":384,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/search_builders.go#L348-L384","documentation":"Recorded (and later returned by Run) when AggregateBuilder.SortByMax is called but the most recent pipeline step is not a SORTBY step. MAX <n> is a modifier on the last SORTBY; calling it without one (or after another step type) is a misuse and the error is deferred to Run.","triggerScenarios":"Calling b.SortByMax(n) when the builder has no steps or its last step is a GroupBy/Load/Filter rather than SortBy.","commonSituations":"Reordered pipeline where SortByMax was placed before SortBy; refactoring removed the SortBy call but left SortByMax; chaining SortByMax after a GroupBy reducer step.","solutions":["Call b.SortBy(field, asc) immediately before b.SortByMax(n) so the last step is the SORTBY being modified.","If you need MAX on a new SORTBY, ensure the SortBy call that starts the step comes first.","Add a builder-order test asserting the step preceding SortByMax is a SortBy."],"exampleFix":"// before\nb.SortByMax(10) // no SortBy step -> error\n\n// after\nb.SortBy(\"@price\", false).SortByMax(10)","handlingStrategy":"validation","validationCode":"// MAX modifies the last SORTBY; ensure one exists.\nif len(steps) == 0 || steps[len(steps)-1].SortBy == nil {\n    return errors.New(\"SortByMax requires a preceding SortBy\")\n}","typeGuard":null,"tryCatchPattern":"res, err := b.Run()\nif err != nil && strings.Contains(err.Error(), \"SortByMax must follow a SortBy step\") {\n    // add SortBy before SortByMax\n}","preventionTips":["Chain SortBy(...).SortByMax(n) together so the dependency is visually explicit.","Add builder-order assertions in tests.","Treat a SortByMax ordering error as a compile-time-style bug to fix in code, not handle at runtime."],"tags":["ftaggregate","search","builder","validation","api-misuse"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}