mirror of
https://github.com/hanzokv/go.git
synced 2026-09-24 00:31:51 +00:00
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:
+25
-2
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user