From f3c2711fe1e5f8402384f61a817a637b5cd42099 Mon Sep 17 00:00:00 2001 From: alingse Date: Thu, 20 Feb 2025 22:22:34 +0800 Subject: [PATCH 1/4] move regexp.MustCompile close to call (#3280) * move regexp.MustCompile out of func * move moduleRe close to call --------- Co-authored-by: Nedyalko Dyakov --- command.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/command.go b/command.go index 2623a2396..696501453 100644 --- a/command.go +++ b/command.go @@ -5492,8 +5492,6 @@ func (cmd *InfoCmd) readReply(rd *proto.Reader) error { section := "" scanner := bufio.NewScanner(strings.NewReader(val)) - moduleRe := regexp.MustCompile(`module:name=(.+?),(.+)$`) - for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "#") { @@ -5504,6 +5502,7 @@ func (cmd *InfoCmd) readReply(rd *proto.Reader) error { cmd.val[section] = make(map[string]string) } else if line != "" { if section == "Modules" { + moduleRe := regexp.MustCompile(`module:name=(.+?),(.+)$`) kv := moduleRe.FindStringSubmatch(line) if len(kv) == 3 { cmd.val[section][kv[1]] = kv[2] From 747190e2311ccb57c76a9b0f2c58c79674b33561 Mon Sep 17 00:00:00 2001 From: andy-stark-redis <164213578+andy-stark-redis@users.noreply.github.com> Date: Thu, 20 Feb 2025 14:23:05 +0000 Subject: [PATCH 2/4] DOC-4329 added range query examples (#3252) Co-authored-by: Nedyalko Dyakov --- doctests/query_range_test.go | 376 +++++++++++++++++++++++++++++++++++ 1 file changed, 376 insertions(+) create mode 100644 doctests/query_range_test.go diff --git a/doctests/query_range_test.go b/doctests/query_range_test.go new file mode 100644 index 000000000..41438ff0e --- /dev/null +++ b/doctests/query_range_test.go @@ -0,0 +1,376 @@ +// EXAMPLE: query_range +// HIDE_START +package example_commands_test + +import ( + "context" + "fmt" + + "github.com/redis/go-redis/v9" +) + +func ExampleClient_query_range() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password docs + DB: 0, // use default DB + Protocol: 2, + }) + + // HIDE_END + // REMOVE_START + rdb.FTDropIndex(ctx, "idx:bicycle") + rdb.FTDropIndex(ctx, "idx:email") + // REMOVE_END + + _, err := rdb.FTCreate(ctx, "idx:bicycle", + &redis.FTCreateOptions{ + OnJSON: true, + Prefix: []interface{}{"bicycle:"}, + }, + &redis.FieldSchema{ + FieldName: "$.brand", + As: "brand", + FieldType: redis.SearchFieldTypeText, + }, + &redis.FieldSchema{ + FieldName: "$.model", + As: "model", + FieldType: redis.SearchFieldTypeText, + }, + &redis.FieldSchema{ + FieldName: "$.description", + As: "description", + FieldType: redis.SearchFieldTypeText, + }, + &redis.FieldSchema{ + FieldName: "$.price", + As: "price", + FieldType: redis.SearchFieldTypeNumeric, + }, + &redis.FieldSchema{ + FieldName: "$.condition", + As: "condition", + FieldType: redis.SearchFieldTypeTag, + }, + ).Result() + + if err != nil { + panic(err) + } + + exampleJsons := []map[string]interface{}{ + { + "pickup_zone": "POLYGON((-74.0610 40.7578, -73.9510 40.7578, -73.9510 40.6678, " + + "-74.0610 40.6678, -74.0610 40.7578))", + "store_location": "-74.0060,40.7128", + "brand": "Velorim", + "model": "Jigger", + "price": 270, + "description": "Small and powerful, the Jigger is the best ride for the smallest of tikes! " + + "This is the tiniest kids pedal bike on the market available without a coaster brake, the Jigger " + + "is the vehicle of choice for the rare tenacious little rider raring to go.", + "condition": "new", + }, + { + "pickup_zone": "POLYGON((-118.2887 34.0972, -118.1987 34.0972, -118.1987 33.9872, " + + "-118.2887 33.9872, -118.2887 34.0972))", + "store_location": "-118.2437,34.0522", + "brand": "Bicyk", + "model": "Hillcraft", + "price": 1200, + "description": "Kids want to ride with as little weight as possible. Especially " + + "on an incline! They may be at the age when a 27.5'' wheel bike is just too clumsy coming " + + "off a 24'' bike. The Hillcraft 26 is just the solution they need!", + "condition": "used", + }, + { + "pickup_zone": "POLYGON((-87.6848 41.9331, -87.5748 41.9331, -87.5748 41.8231, " + + "-87.6848 41.8231, -87.6848 41.9331))", + "store_location": "-87.6298,41.8781", + "brand": "Nord", + "model": "Chook air 5", + "price": 815, + "description": "The Chook Air 5 gives kids aged six years and older a durable " + + "and uberlight mountain bike for their first experience on tracks and easy cruising through " + + "forests and fields. The lower top tube makes it easy to mount and dismount in any " + + "situation, giving your kids greater safety on the trails.", + "condition": "used", + }, + { + "pickup_zone": "POLYGON((-80.2433 25.8067, -80.1333 25.8067, -80.1333 25.6967, " + + "-80.2433 25.6967, -80.2433 25.8067))", + "store_location": "-80.1918,25.7617", + "brand": "Eva", + "model": "Eva 291", + "price": 3400, + "description": "The sister company to Nord, Eva launched in 2005 as the first " + + "and only women-dedicated bicycle brand. Designed by women for women, allEva bikes " + + "are optimized for the feminine physique using analytics from a body metrics database. " + + "If you like 29ers, try the Eva 291. It’s a brand new bike for 2022.. This " + + "full-suspension, cross-country ride has been designed for velocity. The 291 has " + + "100mm of front and rear travel, a superlight aluminum frame and fast-rolling " + + "29-inch wheels. Yippee!", + "condition": "used", + }, + { + "pickup_zone": "POLYGON((-122.4644 37.8199, -122.3544 37.8199, -122.3544 37.7099, " + + "-122.4644 37.7099, -122.4644 37.8199))", + "store_location": "-122.4194,37.7749", + "brand": "Noka Bikes", + "model": "Kahuna", + "price": 3200, + "description": "Whether you want to try your hand at XC racing or are looking " + + "for a lively trail bike that's just as inspiring on the climbs as it is over rougher " + + "ground, the Wilder is one heck of a bike built specifically for short women. Both the " + + "frames and components have been tweaked to include a women’s saddle, different bars " + + "and unique colourway.", + "condition": "used", + }, + { + "pickup_zone": "POLYGON((-0.1778 51.5524, 0.0822 51.5524, 0.0822 51.4024, " + + "-0.1778 51.4024, -0.1778 51.5524))", + "store_location": "-0.1278,51.5074", + "brand": "Breakout", + "model": "XBN 2.1 Alloy", + "price": 810, + "description": "The XBN 2.1 Alloy is our entry-level road bike – but that’s " + + "not to say that it’s a basic machine. With an internal weld aluminium frame, a full " + + "carbon fork, and the slick-shifting Claris gears from Shimano’s, this is a bike which " + + "doesn’t break the bank and delivers craved performance.", + "condition": "new", + }, + { + "pickup_zone": "POLYGON((2.1767 48.9016, 2.5267 48.9016, 2.5267 48.5516, " + + "2.1767 48.5516, 2.1767 48.9016))", + "store_location": "2.3522,48.8566", + "brand": "ScramBikes", + "model": "WattBike", + "price": 2300, + "description": "The WattBike is the best e-bike for people who still " + + "feel young at heart. It has a Bafang 1000W mid-drive system and a 48V 17.5AH " + + "Samsung Lithium-Ion battery, allowing you to ride for more than 60 miles on one " + + "charge. It’s great for tackling hilly terrain or if you just fancy a more " + + "leisurely ride. With three working modes, you can choose between E-bike, " + + "assisted bicycle, and normal bike modes.", + "condition": "new", + }, + { + "pickup_zone": "POLYGON((13.3260 52.5700, 13.6550 52.5700, 13.6550 52.2700, " + + "13.3260 52.2700, 13.3260 52.5700))", + "store_location": "13.4050,52.5200", + "brand": "Peaknetic", + "model": "Secto", + "price": 430, + "description": "If you struggle with stiff fingers or a kinked neck or " + + "back after a few minutes on the road, this lightweight, aluminum bike alleviates " + + "those issues and allows you to enjoy the ride. From the ergonomic grips to the " + + "lumbar-supporting seat position, the Roll Low-Entry offers incredible comfort. " + + "The rear-inclined seat tube facilitates stability by allowing you to put a foot " + + "on the ground to balance at a stop, and the low step-over frame makes it " + + "accessible for all ability and mobility levels. The saddle is very soft, with " + + "a wide back to support your hip joints and a cutout in the center to redistribute " + + "that pressure. Rim brakes deliver satisfactory braking control, and the wide tires " + + "provide a smooth, stable ride on paved roads and gravel. Rack and fender mounts " + + "facilitate setting up the Roll Low-Entry as your preferred commuter, and the " + + "BMX-like handlebar offers space for mounting a flashlight, bell, or phone holder.", + "condition": "new", + }, + { + "pickup_zone": "POLYGON((1.9450 41.4301, 2.4018 41.4301, 2.4018 41.1987, " + + "1.9450 41.1987, 1.9450 41.4301))", + "store_location": "2.1734, 41.3851", + "brand": "nHill", + "model": "Summit", + "price": 1200, + "description": "This budget mountain bike from nHill performs well both " + + "on bike paths and on the trail. The fork with 100mm of travel absorbs rough " + + "terrain. Fat Kenda Booster tires give you grip in corners and on wet trails. " + + "The Shimano Tourney drivetrain offered enough gears for finding a comfortable " + + "pace to ride uphill, and the Tektro hydraulic disc brakes break smoothly. " + + "Whether you want an affordable bike that you can take to work, but also take " + + "trail in mountains on the weekends or you’re just after a stable, comfortable " + + "ride for the bike path, the Summit gives a good value for money.", + "condition": "new", + }, + { + "pickup_zone": "POLYGON((12.4464 42.1028, 12.5464 42.1028, " + + "12.5464 41.7028, 12.4464 41.7028, 12.4464 42.1028))", + "store_location": "12.4964,41.9028", + "model": "ThrillCycle", + "brand": "BikeShind", + "price": 815, + "description": "An artsy, retro-inspired bicycle that’s as " + + "functional as it is pretty: The ThrillCycle steel frame offers a smooth ride. " + + "A 9-speed drivetrain has enough gears for coasting in the city, but we wouldn’t " + + "suggest taking it to the mountains. Fenders protect you from mud, and a rear " + + "basket lets you transport groceries, flowers and books. The ThrillCycle comes " + + "with a limited lifetime warranty, so this little guy will last you long " + + "past graduation.", + "condition": "refurbished", + }, + } + + for i, json := range exampleJsons { + _, err := rdb.JSONSet(ctx, fmt.Sprintf("bicycle:%v", i), "$", json).Result() + + if err != nil { + panic(err) + } + } + + // STEP_START range1 + res1, err := rdb.FTSearchWithArgs(ctx, + "idx:bicycle", "@price:[500 1000]", + &redis.FTSearchOptions{ + Return: []redis.FTSearchReturn{ + { + FieldName: "price", + }, + }, + }, + ).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res1.Total) // >>> 3 + + for _, doc := range res1.Docs { + fmt.Printf("%v : price %v\n", doc.ID, doc.Fields["price"]) + } + // >>> bicycle:2 : price 815 + // >>> bicycle:5 : price 810 + // >>> bicycle:9 : price 815 + // STEP_END + + // STEP_START range2 + res2, err := rdb.FTSearchWithArgs(ctx, + "idx:bicycle", "*", + &redis.FTSearchOptions{ + Filters: []redis.FTSearchFilter{ + { + FieldName: "price", + Min: 500, + Max: 1000, + }, + }, + Return: []redis.FTSearchReturn{ + { + FieldName: "price", + }, + }, + }, + ).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res2.Total) // >>> 3 + + for _, doc := range res2.Docs { + fmt.Printf("%v : price %v\n", doc.ID, doc.Fields["price"]) + } + // >>> bicycle:2 : price 815 + // >>> bicycle:5 : price 810 + // >>> bicycle:9 : price 815 + // STEP_END + + // STEP_START range3 + res3, err := rdb.FTSearchWithArgs(ctx, + "idx:bicycle", "*", + &redis.FTSearchOptions{ + Return: []redis.FTSearchReturn{ + { + FieldName: "price", + }, + }, + Filters: []redis.FTSearchFilter{ + { + FieldName: "price", + Min: "(1000", + Max: "+inf", + }, + }, + }, + ).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res3.Total) // >>> 5 + + for _, doc := range res3.Docs { + fmt.Printf("%v : price %v\n", doc.ID, doc.Fields["price"]) + } + // >>> bicycle:1 : price 1200 + // >>> bicycle:4 : price 3200 + // >>> bicycle:6 : price 2300 + // >>> bicycle:3 : price 3400 + // >>> bicycle:8 : price 1200 + // STEP_END + + // STEP_START range4 + res4, err := rdb.FTSearchWithArgs(ctx, + "idx:bicycle", + "@price:[-inf 2000]", + &redis.FTSearchOptions{ + Return: []redis.FTSearchReturn{ + { + FieldName: "price", + }, + }, + SortBy: []redis.FTSearchSortBy{ + { + FieldName: "price", + Asc: true, + }, + }, + LimitOffset: 0, + Limit: 5, + }, + ).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res4.Total) // >>> 7 + + for _, doc := range res4.Docs { + fmt.Printf("%v : price %v\n", doc.ID, doc.Fields["price"]) + } + // >>> bicycle:0 : price 270 + // >>> bicycle:7 : price 430 + // >>> bicycle:5 : price 810 + // >>> bicycle:2 : price 815 + // >>> bicycle:9 : price 815 + // STEP_END + + // Output: + // 3 + // bicycle:2 : price 815 + // bicycle:5 : price 810 + // bicycle:9 : price 815 + // 3 + // bicycle:2 : price 815 + // bicycle:5 : price 810 + // bicycle:9 : price 815 + // 5 + // bicycle:1 : price 1200 + // bicycle:4 : price 3200 + // bicycle:6 : price 2300 + // bicycle:3 : price 3400 + // bicycle:8 : price 1200 + // 7 + // bicycle:0 : price 270 + // bicycle:7 : price 430 + // bicycle:5 : price 810 + // bicycle:2 : price 815 + // bicycle:9 : price 815 +} From 37accb4b2807e1bec0d90079fd5e1506882cf6fe Mon Sep 17 00:00:00 2001 From: Ali Error Date: Thu, 20 Feb 2025 17:54:11 +0300 Subject: [PATCH 3/4] fix: nil pointer dereferencing in writeArg (#3271) * fixed bug with nil dereferencing in writeArg, added hset struct example, added tests * removed password from example * added omitempty * reverted xxhash versioning * reverted xxhash versioning * removed password * removed password --------- Co-authored-by: Nedyalko Dyakov --- example/hset-struct/README.md | 7 ++ example/hset-struct/go.mod | 15 ++++ example/hset-struct/go.sum | 10 +++ example/hset-struct/main.go | 129 ++++++++++++++++++++++++++++++++++ example/scan-struct/main.go | 6 ++ internal/proto/writer.go | 53 ++++++++++++++ internal/proto/writer_test.go | 83 ++++++++++++++-------- 7 files changed, 274 insertions(+), 29 deletions(-) create mode 100644 example/hset-struct/README.md create mode 100644 example/hset-struct/go.mod create mode 100644 example/hset-struct/go.sum create mode 100644 example/hset-struct/main.go diff --git a/example/hset-struct/README.md b/example/hset-struct/README.md new file mode 100644 index 000000000..e6cb4523c --- /dev/null +++ b/example/hset-struct/README.md @@ -0,0 +1,7 @@ +# Example for setting struct fields as hash fields + +To run this example: + +```shell +go run . +``` diff --git a/example/hset-struct/go.mod b/example/hset-struct/go.mod new file mode 100644 index 000000000..fca1a5972 --- /dev/null +++ b/example/hset-struct/go.mod @@ -0,0 +1,15 @@ +module github.com/redis/go-redis/example/scan-struct + +go 1.18 + +replace github.com/redis/go-redis/v9 => ../.. + +require ( + github.com/davecgh/go-spew v1.1.1 + github.com/redis/go-redis/v9 v9.6.2 +) + +require ( + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect +) diff --git a/example/hset-struct/go.sum b/example/hset-struct/go.sum new file mode 100644 index 000000000..1602e702e --- /dev/null +++ b/example/hset-struct/go.sum @@ -0,0 +1,10 @@ +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= diff --git a/example/hset-struct/main.go b/example/hset-struct/main.go new file mode 100644 index 000000000..2e08f542f --- /dev/null +++ b/example/hset-struct/main.go @@ -0,0 +1,129 @@ +package main + +import ( + "context" + "time" + + "github.com/davecgh/go-spew/spew" + + "github.com/redis/go-redis/v9" +) + +type Model struct { + Str1 string `redis:"str1"` + Str2 string `redis:"str2"` + Str3 *string `redis:"str3"` + Str4 *string `redis:"str4"` + Bytes []byte `redis:"bytes"` + Int int `redis:"int"` + Int2 *int `redis:"int2"` + Int3 *int `redis:"int3"` + Bool bool `redis:"bool"` + Bool2 *bool `redis:"bool2"` + Bool3 *bool `redis:"bool3"` + Bool4 *bool `redis:"bool4,omitempty"` + Time time.Time `redis:"time"` + Time2 *time.Time `redis:"time2"` + Time3 *time.Time `redis:"time3"` + Ignored struct{} `redis:"-"` +} + +func main() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: ":6379", + }) + + _ = rdb.FlushDB(ctx).Err() + + t := time.Date(2025, 02, 8, 0, 0, 0, 0, time.UTC) + + data := Model{ + Str1: "hello", + Str2: "world", + Str3: ToPtr("hello"), + Str4: nil, + Bytes: []byte("this is bytes !"), + Int: 123, + Int2: ToPtr(0), + Int3: nil, + Bool: true, + Bool2: ToPtr(false), + Bool3: nil, + Time: t, + Time2: ToPtr(t), + Time3: nil, + Ignored: struct{}{}, + } + + // Set some fields. + if _, err := rdb.Pipelined(ctx, func(rdb redis.Pipeliner) error { + rdb.HMSet(ctx, "key", data) + return nil + }); err != nil { + panic(err) + } + + var model1, model2 Model + + // Scan all fields into the model. + if err := rdb.HGetAll(ctx, "key").Scan(&model1); err != nil { + panic(err) + } + + // Or scan a subset of the fields. + if err := rdb.HMGet(ctx, "key", "str1", "int").Scan(&model2); err != nil { + panic(err) + } + + spew.Dump(model1) + // Output: + // (main.Model) { + // Str1: (string) (len=5) "hello", + // Str2: (string) (len=5) "world", + // Str3: (*string)(0xc000016970)((len=5) "hello"), + // Str4: (*string)(0xc000016980)(""), + // Bytes: ([]uint8) (len=15 cap=16) { + // 00000000 74 68 69 73 20 69 73 20 62 79 74 65 73 20 21 |this is bytes !| + // }, + // Int: (int) 123, + // Int2: (*int)(0xc000014568)(0), + // Int3: (*int)(0xc000014560)(0), + // Bool: (bool) true, + // Bool2: (*bool)(0xc000014570)(false), + // Bool3: (*bool)(0xc000014548)(false), + // Bool4: (*bool)(), + // Time: (time.Time) 2025-02-08 00:00:00 +0000 UTC, + // Time2: (*time.Time)(0xc0000122a0)(2025-02-08 00:00:00 +0000 UTC), + // Time3: (*time.Time)(0xc000012288)(0001-01-01 00:00:00 +0000 UTC), + // Ignored: (struct {}) { + // } + // } + + spew.Dump(model2) + // Output: + // (main.Model) { + // Str1: (string) (len=5) "hello", + // Str2: (string) "", + // Str3: (*string)(), + // Str4: (*string)(), + // Bytes: ([]uint8) , + // Int: (int) 123, + // Int2: (*int)(), + // Int3: (*int)(), + // Bool: (bool) false, + // Bool2: (*bool)(), + // Bool3: (*bool)(), + // Bool4: (*bool)(), + // Time: (time.Time) 0001-01-01 00:00:00 +0000 UTC, + // Time2: (*time.Time)(), + // Time3: (*time.Time)(), + // Ignored: (struct {}) { + // } + // } +} + +func ToPtr[T any](v T) *T { + return &v +} diff --git a/example/scan-struct/main.go b/example/scan-struct/main.go index cc877b847..2dc5b85c1 100644 --- a/example/scan-struct/main.go +++ b/example/scan-struct/main.go @@ -11,9 +11,12 @@ import ( type Model struct { Str1 string `redis:"str1"` Str2 string `redis:"str2"` + Str3 *string `redis:"str3"` Bytes []byte `redis:"bytes"` Int int `redis:"int"` + Int2 *int `redis:"int2"` Bool bool `redis:"bool"` + Bool2 *bool `redis:"bool2"` Ignored struct{} `redis:"-"` } @@ -29,8 +32,11 @@ func main() { if _, err := rdb.Pipelined(ctx, func(rdb redis.Pipeliner) error { rdb.HSet(ctx, "key", "str1", "hello") rdb.HSet(ctx, "key", "str2", "world") + rdb.HSet(ctx, "key", "str3", "") rdb.HSet(ctx, "key", "int", 123) + rdb.HSet(ctx, "key", "int2", 0) rdb.HSet(ctx, "key", "bool", 1) + rdb.HSet(ctx, "key", "bool2", 0) rdb.HSet(ctx, "key", "bytes", []byte("this is bytes !")) return nil }); err != nil { diff --git a/internal/proto/writer.go b/internal/proto/writer.go index 78595cc4f..38e66c688 100644 --- a/internal/proto/writer.go +++ b/internal/proto/writer.go @@ -66,56 +66,95 @@ func (w *Writer) WriteArg(v interface{}) error { case string: return w.string(v) case *string: + if v == nil { + return w.string("") + } return w.string(*v) case []byte: return w.bytes(v) case int: return w.int(int64(v)) case *int: + if v == nil { + return w.int(0) + } return w.int(int64(*v)) case int8: return w.int(int64(v)) case *int8: + if v == nil { + return w.int(0) + } return w.int(int64(*v)) case int16: return w.int(int64(v)) case *int16: + if v == nil { + return w.int(0) + } return w.int(int64(*v)) case int32: return w.int(int64(v)) case *int32: + if v == nil { + return w.int(0) + } return w.int(int64(*v)) case int64: return w.int(v) case *int64: + if v == nil { + return w.int(0) + } return w.int(*v) case uint: return w.uint(uint64(v)) case *uint: + if v == nil { + return w.uint(0) + } return w.uint(uint64(*v)) case uint8: return w.uint(uint64(v)) case *uint8: + if v == nil { + return w.string("") + } return w.uint(uint64(*v)) case uint16: return w.uint(uint64(v)) case *uint16: + if v == nil { + return w.uint(0) + } return w.uint(uint64(*v)) case uint32: return w.uint(uint64(v)) case *uint32: + if v == nil { + return w.uint(0) + } return w.uint(uint64(*v)) case uint64: return w.uint(v) case *uint64: + if v == nil { + return w.uint(0) + } return w.uint(*v) case float32: return w.float(float64(v)) case *float32: + if v == nil { + return w.float(0) + } return w.float(float64(*v)) case float64: return w.float(v) case *float64: + if v == nil { + return w.float(0) + } return w.float(*v) case bool: if v { @@ -123,6 +162,9 @@ func (w *Writer) WriteArg(v interface{}) error { } return w.int(0) case *bool: + if v == nil { + return w.int(0) + } if *v { return w.int(1) } @@ -130,8 +172,19 @@ func (w *Writer) WriteArg(v interface{}) error { case time.Time: w.numBuf = v.AppendFormat(w.numBuf[:0], time.RFC3339Nano) return w.bytes(w.numBuf) + case *time.Time: + if v == nil { + v = &time.Time{} + } + w.numBuf = v.AppendFormat(w.numBuf[:0], time.RFC3339Nano) + return w.bytes(w.numBuf) case time.Duration: return w.int(v.Nanoseconds()) + case *time.Duration: + if v == nil { + return w.int(0) + } + return w.int(v.Nanoseconds()) case encoding.BinaryMarshaler: b, err := v.MarshalBinary() if err != nil { diff --git a/internal/proto/writer_test.go b/internal/proto/writer_test.go index 7c9d20884..1d5152dc0 100644 --- a/internal/proto/writer_test.go +++ b/internal/proto/writer_test.go @@ -111,36 +111,61 @@ var _ = Describe("WriteArg", func() { wr = proto.NewWriter(buf) }) + t := time.Date(2025, 2, 8, 00, 00, 00, 0, time.UTC) + args := map[any]string{ - "hello": "$5\r\nhello\r\n", - int(10): "$2\r\n10\r\n", - util.ToPtr(int(10)): "$2\r\n10\r\n", - int8(10): "$2\r\n10\r\n", - util.ToPtr(int8(10)): "$2\r\n10\r\n", - int16(10): "$2\r\n10\r\n", - util.ToPtr(int16(10)): "$2\r\n10\r\n", - int32(10): "$2\r\n10\r\n", - util.ToPtr(int32(10)): "$2\r\n10\r\n", - int64(10): "$2\r\n10\r\n", - util.ToPtr(int64(10)): "$2\r\n10\r\n", - uint(10): "$2\r\n10\r\n", - util.ToPtr(uint(10)): "$2\r\n10\r\n", - uint8(10): "$2\r\n10\r\n", - util.ToPtr(uint8(10)): "$2\r\n10\r\n", - uint16(10): "$2\r\n10\r\n", - util.ToPtr(uint16(10)): "$2\r\n10\r\n", - uint32(10): "$2\r\n10\r\n", - util.ToPtr(uint32(10)): "$2\r\n10\r\n", - uint64(10): "$2\r\n10\r\n", - util.ToPtr(uint64(10)): "$2\r\n10\r\n", - float32(10.3): "$18\r\n10.300000190734863\r\n", - util.ToPtr(float32(10.3)): "$18\r\n10.300000190734863\r\n", - float64(10.3): "$4\r\n10.3\r\n", - util.ToPtr(float64(10.3)): "$4\r\n10.3\r\n", - bool(true): "$1\r\n1\r\n", - bool(false): "$1\r\n0\r\n", - util.ToPtr(bool(true)): "$1\r\n1\r\n", - util.ToPtr(bool(false)): "$1\r\n0\r\n", + "hello": "$5\r\nhello\r\n", + util.ToPtr("hello"): "$5\r\nhello\r\n", + (*string)(nil): "$0\r\n\r\n", + int(10): "$2\r\n10\r\n", + util.ToPtr(int(10)): "$2\r\n10\r\n", + (*int)(nil): "$1\r\n0\r\n", + int8(10): "$2\r\n10\r\n", + util.ToPtr(int8(10)): "$2\r\n10\r\n", + (*int8)(nil): "$1\r\n0\r\n", + int16(10): "$2\r\n10\r\n", + util.ToPtr(int16(10)): "$2\r\n10\r\n", + (*int16)(nil): "$1\r\n0\r\n", + int32(10): "$2\r\n10\r\n", + util.ToPtr(int32(10)): "$2\r\n10\r\n", + (*int32)(nil): "$1\r\n0\r\n", + int64(10): "$2\r\n10\r\n", + util.ToPtr(int64(10)): "$2\r\n10\r\n", + (*int64)(nil): "$1\r\n0\r\n", + uint(10): "$2\r\n10\r\n", + util.ToPtr(uint(10)): "$2\r\n10\r\n", + (*uint)(nil): "$1\r\n0\r\n", + uint8(10): "$2\r\n10\r\n", + util.ToPtr(uint8(10)): "$2\r\n10\r\n", + (*uint8)(nil): "$0\r\n\r\n", + uint16(10): "$2\r\n10\r\n", + util.ToPtr(uint16(10)): "$2\r\n10\r\n", + (*uint16)(nil): "$1\r\n0\r\n", + uint32(10): "$2\r\n10\r\n", + util.ToPtr(uint32(10)): "$2\r\n10\r\n", + (*uint32)(nil): "$1\r\n0\r\n", + uint64(10): "$2\r\n10\r\n", + util.ToPtr(uint64(10)): "$2\r\n10\r\n", + (*uint64)(nil): "$1\r\n0\r\n", + float32(10.3): "$18\r\n10.300000190734863\r\n", + util.ToPtr(float32(10.3)): "$18\r\n10.300000190734863\r\n", + (*float32)(nil): "$1\r\n0\r\n", + float64(10.3): "$4\r\n10.3\r\n", + util.ToPtr(float64(10.3)): "$4\r\n10.3\r\n", + (*float64)(nil): "$1\r\n0\r\n", + bool(true): "$1\r\n1\r\n", + bool(false): "$1\r\n0\r\n", + util.ToPtr(bool(true)): "$1\r\n1\r\n", + util.ToPtr(bool(false)): "$1\r\n0\r\n", + (*bool)(nil): "$1\r\n0\r\n", + time.Time(t): "$20\r\n2025-02-08T00:00:00Z\r\n", + util.ToPtr(time.Time(t)): "$20\r\n2025-02-08T00:00:00Z\r\n", + (*time.Time)(nil): "$20\r\n0001-01-01T00:00:00Z\r\n", + time.Duration(time.Second): "$10\r\n1000000000\r\n", + util.ToPtr(time.Duration(time.Second)): "$10\r\n1000000000\r\n", + (*time.Duration)(nil): "$1\r\n0\r\n", + (encoding.BinaryMarshaler)(&MyType{}): "$5\r\nhello\r\n", + (encoding.BinaryMarshaler)(nil): "$0\r\n\r\n", } for arg, expect := range args { From 30e7388c88e29e11def4302ae740b677a6309019 Mon Sep 17 00:00:00 2001 From: "fengyun.rui" Date: Thu, 20 Feb 2025 22:55:54 +0800 Subject: [PATCH 4/4] fix: race slice for list function of ring client (#2931) * fix: race slice for list of ring client Signed-off-by: rfyiamcool * fix: copy wrong list Co-authored-by: Nedyalko Dyakov --------- Signed-off-by: rfyiamcool Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Co-authored-by: Nedyalko Dyakov --- ring.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ring.go b/ring.go index b40221734..06a26020a 100644 --- a/ring.go +++ b/ring.go @@ -341,7 +341,8 @@ func (c *ringSharding) List() []*ringShard { c.mu.RLock() if !c.closed { - list = c.shards.list + list = make([]*ringShard, len(c.shards.list)) + copy(list, c.shards.list) } c.mu.RUnlock()