1
1
package redis_test
2
2
3
3
import (
4
+ "bytes"
4
5
"context"
6
+ "encoding/binary"
5
7
"fmt"
6
- "strconv "
8
+ "math "
7
9
"strings"
8
10
"time"
9
11
10
12
. "github.com/bsm/ginkgo/v2"
11
13
. "github.com/bsm/gomega"
12
14
"github.com/redis/go-redis/v9"
15
+ "github.com/redis/go-redis/v9/helper"
13
16
)
14
17
15
18
func WaitForIndexing (c * redis.Client , index string ) {
@@ -27,6 +30,14 @@ func WaitForIndexing(c *redis.Client, index string) {
27
30
}
28
31
}
29
32
33
+ func encodeFloat32Vector (vec []float32 ) []byte {
34
+ buf := new (bytes.Buffer )
35
+ for _ , v := range vec {
36
+ binary .Write (buf , binary .LittleEndian , v )
37
+ }
38
+ return buf .Bytes ()
39
+ }
40
+
30
41
var _ = Describe ("RediSearch commands Resp 2" , Label ("search" ), func () {
31
42
ctx := context .TODO ()
32
43
var client * redis.Client
@@ -693,9 +704,9 @@ var _ = Describe("RediSearch commands Resp 2", Label("search"), func() {
693
704
Expect (err ).NotTo (HaveOccurred ())
694
705
Expect (res ).ToNot (BeNil ())
695
706
Expect (len (res .Rows )).To (BeEquivalentTo (2 ))
696
- score1 , err := strconv .ParseFloat (fmt .Sprintf ("%s" , res .Rows [0 ].Fields ["__score" ]), 64 )
707
+ score1 , err := helper .ParseFloat (fmt .Sprintf ("%s" , res .Rows [0 ].Fields ["__score" ]))
697
708
Expect (err ).NotTo (HaveOccurred ())
698
- score2 , err := strconv .ParseFloat (fmt .Sprintf ("%s" , res .Rows [1 ].Fields ["__score" ]), 64 )
709
+ score2 , err := helper .ParseFloat (fmt .Sprintf ("%s" , res .Rows [1 ].Fields ["__score" ]))
699
710
Expect (err ).NotTo (HaveOccurred ())
700
711
Expect (score1 ).To (BeNumerically (">" , score2 ))
701
712
@@ -712,9 +723,9 @@ var _ = Describe("RediSearch commands Resp 2", Label("search"), func() {
712
723
Expect (err ).NotTo (HaveOccurred ())
713
724
Expect (resDM ).ToNot (BeNil ())
714
725
Expect (len (resDM .Rows )).To (BeEquivalentTo (2 ))
715
- score1DM , err := strconv .ParseFloat (fmt .Sprintf ("%s" , resDM .Rows [0 ].Fields ["__score" ]), 64 )
726
+ score1DM , err := helper .ParseFloat (fmt .Sprintf ("%s" , resDM .Rows [0 ].Fields ["__score" ]))
716
727
Expect (err ).NotTo (HaveOccurred ())
717
- score2DM , err := strconv .ParseFloat (fmt .Sprintf ("%s" , resDM .Rows [1 ].Fields ["__score" ]), 64 )
728
+ score2DM , err := helper .ParseFloat (fmt .Sprintf ("%s" , resDM .Rows [1 ].Fields ["__score" ]))
718
729
Expect (err ).NotTo (HaveOccurred ())
719
730
Expect (score1DM ).To (BeNumerically (">" , score2DM ))
720
731
@@ -1684,6 +1695,56 @@ var _ = Describe("RediSearch commands Resp 2", Label("search"), func() {
1684
1695
Expect (resUint8 .Docs [0 ].ID ).To (BeEquivalentTo ("doc1" ))
1685
1696
})
1686
1697
1698
+ It ("should return special float scores in FT.SEARCH vecsim" , Label ("search" , "ftsearch" , "vecsim" ), func () {
1699
+ SkipBeforeRedisVersion (7.4 , "doesn't work with older redis stack images" )
1700
+
1701
+ vecField := & redis.FTFlatOptions {
1702
+ Type : "FLOAT32" ,
1703
+ Dim : 2 ,
1704
+ DistanceMetric : "IP" ,
1705
+ }
1706
+ _ , err := client .FTCreate (ctx , "idx_vec" ,
1707
+ & redis.FTCreateOptions {OnHash : true , Prefix : []interface {}{"doc:" }},
1708
+ & redis.FieldSchema {FieldName : "vector" , FieldType : redis .SearchFieldTypeVector , VectorArgs : & redis.FTVectorArgs {FlatOptions : vecField }}).Result ()
1709
+ Expect (err ).NotTo (HaveOccurred ())
1710
+ WaitForIndexing (client , "idx_vec" )
1711
+
1712
+ bigPos := []float32 {1e38 , 1e38 }
1713
+ bigNeg := []float32 {- 1e38 , - 1e38 }
1714
+ nanVec := []float32 {float32 (math .NaN ()), 0 }
1715
+ negNanVec := []float32 {float32 (math .Copysign (math .NaN (), - 1 )), 0 }
1716
+
1717
+ client .HSet (ctx , "doc:1" , "vector" , encodeFloat32Vector (bigPos ))
1718
+ client .HSet (ctx , "doc:2" , "vector" , encodeFloat32Vector (bigNeg ))
1719
+ client .HSet (ctx , "doc:3" , "vector" , encodeFloat32Vector (nanVec ))
1720
+ client .HSet (ctx , "doc:4" , "vector" , encodeFloat32Vector (negNanVec ))
1721
+
1722
+ searchOptions := & redis.FTSearchOptions {WithScores : true , Params : map [string ]interface {}{"vec" : encodeFloat32Vector (bigPos )}}
1723
+ res , err := client .FTSearchWithArgs (ctx , "idx_vec" , "*=>[KNN 4 @vector $vec]" , searchOptions ).Result ()
1724
+ Expect (err ).NotTo (HaveOccurred ())
1725
+ Expect (res .Total ).To (BeEquivalentTo (4 ))
1726
+
1727
+ var scores []float64
1728
+ for _ , row := range res .Docs {
1729
+ raw := fmt .Sprintf ("%v" , row .Fields ["__vector_score" ])
1730
+ f , err := helper .ParseFloat (raw )
1731
+ Expect (err ).NotTo (HaveOccurred ())
1732
+ scores = append (scores , f )
1733
+ }
1734
+
1735
+ Expect (scores ).To (ContainElement (BeNumerically ("==" , math .Inf (1 ))))
1736
+ Expect (scores ).To (ContainElement (BeNumerically ("==" , math .Inf (- 1 ))))
1737
+
1738
+ // For NaN values, use a custom check since NaN != NaN in floating point math
1739
+ nanCount := 0
1740
+ for _ , score := range scores {
1741
+ if math .IsNaN (score ) {
1742
+ nanCount ++
1743
+ }
1744
+ }
1745
+ Expect (nanCount ).To (Equal (2 ))
1746
+ })
1747
+
1687
1748
It ("should fail when using a non-zero offset with a zero limit" , Label ("search" , "ftsearch" ), func () {
1688
1749
SkipBeforeRedisVersion (7.9 , "requires Redis 8.x" )
1689
1750
val , err := client .FTCreate (ctx , "testIdx" , & redis.FTCreateOptions {}, & redis.FieldSchema {
0 commit comments