diff --git a/command.go b/command.go index e0e23962fe..7713fdaae1 100644 --- a/command.go +++ b/command.go @@ -7591,7 +7591,7 @@ type VectorScoreSliceCmd struct { var _ Cmder = (*VectorScoreSliceCmd)(nil) -func NewVectorInfoSliceCmd(ctx context.Context, args ...any) *VectorScoreSliceCmd { +func NewVectorScoreSliceCmd(ctx context.Context, args ...any) *VectorScoreSliceCmd { return &VectorScoreSliceCmd{ baseCmd: baseCmd{ ctx: ctx, @@ -7600,6 +7600,11 @@ func NewVectorInfoSliceCmd(ctx context.Context, args ...any) *VectorScoreSliceCm } } +// NewVectorInfoSliceCmd is an alias for NewVectorScoreSliceCmd kept for backwards compatibility. +func NewVectorInfoSliceCmd(ctx context.Context, args ...any) *VectorScoreSliceCmd { + return NewVectorScoreSliceCmd(ctx, args...) +} + func (cmd *VectorScoreSliceCmd) SetVal(val []VectorScore) { cmd.val = val } @@ -7617,11 +7622,29 @@ func (cmd *VectorScoreSliceCmd) String() string { } func (cmd *VectorScoreSliceCmd) readReply(rd *proto.Reader) error { - n, err := rd.ReadMapLen() + typ, err := rd.PeekReplyType() if err != nil { return err } + var n int + if typ == proto.RespMap { + n, err = rd.ReadMapLen() + if err != nil { + return err + } + } else { + // RESP2 returns a flat array [name, score, name, score, ...] + n, err = rd.ReadArrayLen() + if err != nil { + return err + } + if n%2 != 0 { + return fmt.Errorf("redis: VectorScoreSliceCmd expects even number of elements, got %d", n) + } + n /= 2 + } + cmd.val = make([]VectorScore, n) for i := 0; i < n; i++ { name, err := rd.ReadString() diff --git a/vectorset_commands_test.go b/vectorset_commands_test.go index 9dbc8a78f9..438ac9551d 100644 --- a/vectorset_commands_test.go +++ b/vectorset_commands_test.go @@ -3,8 +3,11 @@ package redis import ( "context" "encoding/json" + "fmt" "reflect" "testing" + + "github.com/redis/go-redis/v9/internal/proto" ) func TestVectorFP32_Value(t *testing.T) { @@ -540,3 +543,66 @@ func TestVSimArgs_IndividualOptions(t *testing.T) { }) } } + +// TestVectorScoreSliceCmdReadReply tests that VectorScoreSliceCmd.readReply handles +// both RESP2 (flat array) and RESP3 (map) protocol responses. +func TestVectorScoreSliceCmdReadReply(t *testing.T) { + tests := []struct { + name string + respData []byte + want []VectorScore + wantErr bool + }{ + { + // RESP3 map: %2\r\n +elem1\r\n ,0.9\r\n +elem2\r\n ,0.8\r\n + name: "RESP3 map response", + respData: []byte("%2\r\n+elem1\r\n,0.9\r\n+elem2\r\n,0.8\r\n"), + want: []VectorScore{ + {Name: "elem1", Score: 0.9}, + {Name: "elem2", Score: 0.8}, + }, + }, + { + // RESP2 flat array: *4\r\n $5\r\nelem1\r\n $3\r\n0.9\r\n $5\r\nelem2\r\n $3\r\n0.8\r\n + name: "RESP2 flat array response", + respData: []byte(fmt.Sprintf("*4\r\n$5\r\nelem1\r\n$%d\r\n%s\r\n$5\r\nelem2\r\n$%d\r\n%s\r\n", + len("0.9"), "0.9", len("0.8"), "0.8")), + want: []VectorScore{ + {Name: "elem1", Score: 0.9}, + {Name: "elem2", Score: 0.8}, + }, + }, + { + // RESP3 empty map + name: "RESP3 empty map", + respData: []byte("%0\r\n"), + want: []VectorScore{}, + }, + { + // RESP2 empty array + name: "RESP2 empty array", + respData: []byte("*0\r\n"), + want: []VectorScore{}, + }, + { + // RESP2 odd-length array should return an error + name: "RESP2 odd array returns error", + respData: []byte("*3\r\n$5\r\nelem1\r\n$3\r\n0.9\r\n$5\r\nelem2\r\n"), + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rd := proto.NewReader(newMockConn(tt.respData)) + cmd := NewVectorScoreSliceCmd(context.Background(), "vsim", "key", "withscores") + err := cmd.readReply(rd) + if (err != nil) != tt.wantErr { + t.Fatalf("readReply() error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr && !reflect.DeepEqual(cmd.Val(), tt.want) { + t.Errorf("Val() = %v, want %v", cmd.Val(), tt.want) + } + }) + } +}