mirror of
https://github.com/redis/go-redis.git
synced 2026-10-28 08:24:54 +08:00
feat(command): Populate FT.HYBRID timeout warnings (#3911)
This commit is contained in:
1 parent
507eda5820
commit
5e8e30f5a3
4 files changed
+89
-8
No files matched your search
@@ -168,6 +168,9 @@ func shouldRetry(err error, retryTimeout bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Other server errors are not retried. This includes the logical
|
||||
// -SEARCH_TIMEOUT (search-on-timeout fail): retrying would just repeat the
|
||||
// same expensive query.
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package redis_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
@@ -49,6 +50,8 @@ var _ = Describe("error", func() {
|
||||
proto.ParseErrorReply([]byte("-TRYAGAIN Command cannot be processed, please try again")): true,
|
||||
proto.ParseErrorReply([]byte("-NOREPLICAS Not enough good replicas to write")): true,
|
||||
proto.ParseErrorReply([]byte("-ERR other")): false,
|
||||
// Logical search timeout (search-on-timeout fail): never retried.
|
||||
proto.ParseErrorReply([]byte("-SEARCH_TIMEOUT Timeout limit was reached")): false,
|
||||
}
|
||||
|
||||
for err, expected := range data {
|
||||
@@ -66,4 +69,15 @@ var _ = Describe("error", func() {
|
||||
Expect(redis.ShouldRetry(t2, true)).To(Equal(true))
|
||||
Expect(redis.ShouldRetry(t2, false)).To(Equal(true))
|
||||
})
|
||||
|
||||
It("should not retry a logical search timeout", func() {
|
||||
// -SEARCH_TIMEOUT (search-on-timeout fail) is never retried, plain or wrapped.
|
||||
err := proto.ParseErrorReply([]byte("-SEARCH_TIMEOUT Timeout limit was reached"))
|
||||
Expect(redis.ShouldRetry(err, false)).To(BeFalse())
|
||||
Expect(redis.ShouldRetry(err, true)).To(BeFalse())
|
||||
|
||||
wrapped := fmt.Errorf("query failed: %w", err)
|
||||
Expect(redis.ShouldRetry(wrapped, false)).To(BeFalse())
|
||||
Expect(redis.ShouldRetry(wrapped, true)).To(BeFalse())
|
||||
})
|
||||
})
|
||||
+18
-8
@@ -494,8 +494,10 @@ type FTSynDumpCmd struct {
|
||||
// FTAggregateResult represents the result of an aggregate operation
|
||||
// NOTE: For RESP3 Total is not reliable (before Redis 8.8)
|
||||
type FTAggregateResult struct {
|
||||
Total int
|
||||
Rows []AggregateRow
|
||||
Total int
|
||||
Rows []AggregateRow
|
||||
// Warnings holds server warnings for a partial result (search-on-timeout
|
||||
// return/return-strict). RESP3 only; the fail policy returns an error instead.
|
||||
Warnings []string
|
||||
}
|
||||
|
||||
@@ -626,8 +628,10 @@ type SpellCheckSuggestion struct {
|
||||
}
|
||||
|
||||
type FTSearchResult struct {
|
||||
Total int
|
||||
Docs []Document
|
||||
Total int
|
||||
Docs []Document
|
||||
// Warnings holds server warnings for a partial result (search-on-timeout
|
||||
// return/return-strict). RESP3 only; the fail policy returns an error instead.
|
||||
Warnings []string
|
||||
}
|
||||
|
||||
@@ -2897,8 +2901,10 @@ func (cmd *FTSearchCmd) Clone() Cmder {
|
||||
|
||||
// FTHybridResult represents the result of a hybrid search operation
|
||||
type FTHybridResult struct {
|
||||
TotalResults int
|
||||
Results []map[string]interface{}
|
||||
TotalResults int
|
||||
Results []map[string]interface{}
|
||||
// Warnings holds server warnings for a partial result (search-on-timeout
|
||||
// return/return-strict), on RESP2 and RESP3; the fail policy returns an error.
|
||||
Warnings []string
|
||||
ExecutionTime float64
|
||||
}
|
||||
@@ -3041,9 +3047,13 @@ func parseFTHybrid(data []interface{}, withCursor bool) (FTHybridResult, *FTHybr
|
||||
results = append(results, itemMap)
|
||||
}
|
||||
|
||||
// Parse warnings (optional field)
|
||||
// Optional warnings; accept both "warning" (as FT.SEARCH/FT.AGGREGATE) and "warnings".
|
||||
var warnings []string
|
||||
if warningsData, ok := resultMap["warnings"].([]interface{}); ok {
|
||||
warningsData, ok := resultMap["warning"].([]interface{})
|
||||
if !ok {
|
||||
warningsData, ok = resultMap["warnings"].([]interface{})
|
||||
}
|
||||
if ok {
|
||||
warnings = make([]string, 0, len(warningsData))
|
||||
for _, w := range warningsData {
|
||||
if ws, ok := w.(string); ok {
|
||||
|
||||
@@ -461,3 +461,57 @@ func TestFTHybridWithArgs_ShardKRatio(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestParseFTHybridWarnings checks that FT.HYBRID warnings populate from either
|
||||
// the "warning" or "warnings" key.
|
||||
func TestParseFTHybridWarnings(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []interface{}
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "singular warning key",
|
||||
data: []interface{}{
|
||||
"total_results", int64(0),
|
||||
"results", []interface{}{},
|
||||
"warning", []interface{}{"Timeout limit was reached"},
|
||||
},
|
||||
expected: []string{"Timeout limit was reached"},
|
||||
},
|
||||
{
|
||||
name: "plural warnings key",
|
||||
data: []interface{}{
|
||||
"total_results", int64(0),
|
||||
"results", []interface{}{},
|
||||
"warnings", []interface{}{"Timeout limit was reached"},
|
||||
},
|
||||
expected: []string{"Timeout limit was reached"},
|
||||
},
|
||||
{
|
||||
name: "no warnings",
|
||||
data: []interface{}{
|
||||
"total_results", int64(0),
|
||||
"results", []interface{}{},
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, _, err := parseFTHybrid(tt.data, false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(result.Warnings) != len(tt.expected) {
|
||||
t.Fatalf("len(Warnings) = %d, want %d (%v)", len(result.Warnings), len(tt.expected), result.Warnings)
|
||||
}
|
||||
for i, w := range result.Warnings {
|
||||
if w != tt.expected[i] {
|
||||
t.Errorf("Warnings[%d] = %q, want %q", i, w, tt.expected[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user