fix(vector): VectorScoreSliceCmd to handle RESP2 flat array responses (#3767)

* Initial plan

* Fix VectorScoreSliceCmd to handle RESP2 flat array responses

Agent-Logs-Url: https://github.com/redis/go-redis/sessions/98c71a51-26cc-46ab-b9a0-25bd9c1f3295

Co-authored-by: ndyakov <[email protected]>

* Add NewVectorScoreSliceCmd constructor; fix test to use proper constructor

Agent-Logs-Url: https://github.com/redis/go-redis/sessions/98c71a51-26cc-46ab-b9a0-25bd9c1f3295

Co-authored-by: ndyakov <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: ndyakov <[email protected]>
This commit is contained in:
Copilot
2026-04-09 15:29:57 +03:00
committed by GitHub
co-authored by copilot-swe-agent[bot] <198982749+[email protected]> ndyakov
parent c4770cde7c
commit a6741de03e
2 changed files with 91 additions and 2 deletions
+25 -2
View File
@@ -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()
+66
View File
@@ -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)
}
})
}
}