From 4bd5d417ca593abff98bbb49cbb6354e6f9bca6f Mon Sep 17 00:00:00 2001 From: Hui Date: Tue, 29 Apr 2025 05:16:53 +0800 Subject: [PATCH 1/4] feat: func isEmptyValue support time.Time (#3273) * fix:func isEmptyValue support time.Time * fix: Improve HSet unit tests * feat: Improve HSet unit tests * fix: isEmptyValue Struct only support time.Time * test(hset): add empty custom struct test --------- Co-authored-by: Guo Hui Co-authored-by: Nedyalko Dyakov --- commands.go | 6 +++++ commands_test.go | 69 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/commands.go b/commands.go index bca7d7ee..27132324 100644 --- a/commands.go +++ b/commands.go @@ -155,6 +155,12 @@ func isEmptyValue(v reflect.Value) bool { return v.Float() == 0 case reflect.Interface, reflect.Pointer: return v.IsNil() + case reflect.Struct: + if v.Type() == reflect.TypeOf(time.Time{}) { + return v.IsZero() + } + // Only supports the struct time.Time, + // subsequent iterations will follow the func Scan support decoder. } return false } diff --git a/commands_test.go b/commands_test.go index 6a76756a..8b2aa37d 100644 --- a/commands_test.go +++ b/commands_test.go @@ -2578,6 +2578,63 @@ var _ = Describe("Commands", func() { "val2", "val", })) + + type setOmitEmpty struct { + Set1 string `redis:"set1"` + Set2 int `redis:"set2,omitempty"` + Set3 time.Duration `redis:"set3,omitempty"` + Set4 string `redis:"set4,omitempty"` + Set5 time.Time `redis:"set5,omitempty"` + Set6 *numberStruct `redis:"set6,omitempty"` + Set7 numberStruct `redis:"set7,omitempty"` + } + + hSet = client.HSet(ctx, "hash3", &setOmitEmpty{ + Set1: "val", + }) + Expect(hSet.Err()).NotTo(HaveOccurred()) + // both set1 and set7 are set + // custom struct is not omitted + Expect(hSet.Val()).To(Equal(int64(2))) + + hGetAll := client.HGetAll(ctx, "hash3") + Expect(hGetAll.Err()).NotTo(HaveOccurred()) + Expect(hGetAll.Val()).To(Equal(map[string]string{ + "set1": "val", + "set7": `{"Number":0}`, + })) + var hash3 setOmitEmpty + Expect(hGetAll.Scan(&hash3)).NotTo(HaveOccurred()) + Expect(hash3.Set1).To(Equal("val")) + Expect(hash3.Set2).To(Equal(0)) + Expect(hash3.Set3).To(Equal(time.Duration(0))) + Expect(hash3.Set4).To(Equal("")) + Expect(hash3.Set5).To(Equal(time.Time{})) + Expect(hash3.Set6).To(BeNil()) + Expect(hash3.Set7).To(Equal(numberStruct{})) + + now := time.Now() + hSet = client.HSet(ctx, "hash4", setOmitEmpty{ + Set1: "val", + Set5: now, + Set6: &numberStruct{ + Number: 5, + }, + Set7: numberStruct{ + Number: 3, + }, + }) + Expect(hSet.Err()).NotTo(HaveOccurred()) + Expect(hSet.Val()).To(Equal(int64(4))) + + hGetAll = client.HGetAll(ctx, "hash4") + Expect(hGetAll.Err()).NotTo(HaveOccurred()) + Expect(hGetAll.Val()).To(Equal(map[string]string{ + "set1": "val", + "set5": now.Format(time.RFC3339Nano), + "set6": `{"Number":5}`, + "set7": `{"Number":3}`, + })) }) It("should HSetNX", func() { @@ -7619,12 +7676,16 @@ type numberStruct struct { Number int } -func (s *numberStruct) MarshalBinary() ([]byte, error) { - return json.Marshal(s) +func (n numberStruct) MarshalBinary() ([]byte, error) { + return json.Marshal(n) } -func (s *numberStruct) UnmarshalBinary(b []byte) error { - return json.Unmarshal(b, s) +func (n *numberStruct) UnmarshalBinary(b []byte) error { + return json.Unmarshal(b, n) +} + +func (n *numberStruct) ScanRedis(str string) error { + return json.Unmarshal([]byte(str), n) } func deref(viface interface{}) interface{} { From bb8d50848185fe660c7f0acc3eec5aeebb5f3727 Mon Sep 17 00:00:00 2001 From: fukua95 Date: Tue, 29 Apr 2025 14:39:26 +0800 Subject: [PATCH 2/4] fix: `PubSub` isn't concurrency-safe (#3360) --- pubsub.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pubsub.go b/pubsub.go index 20c085f1..2a0e7a81 100644 --- a/pubsub.go +++ b/pubsub.go @@ -45,6 +45,9 @@ func (c *PubSub) init() { } func (c *PubSub) String() string { + c.mu.Lock() + defer c.mu.Unlock() + channels := mapKeys(c.channels) channels = append(channels, mapKeys(c.patterns)...) channels = append(channels, mapKeys(c.schannels)...) From 2f0a9b720a1cde3cae709ec3423498177b3b0ee7 Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Tue, 29 Apr 2025 12:53:06 +0300 Subject: [PATCH 3/4] migrate golangci-lint config to v2 format (#3354) * migrate golangci-lint config to v2 format * chore: skip CI on migration [skip ci] * Bump golangci version * Address several golangci-lint/staticcheck warnings * change staticchecks settings --- .github/workflows/golangci-lint.yml | 4 ++-- .golangci.yml | 31 +++++++++++++++++++++++++++++ command.go | 5 +++-- extra/rediscensus/go.mod | 2 +- extra/rediscmd/go.mod | 2 +- extra/redisotel/go.mod | 2 +- extra/redisprometheus/go.mod | 2 +- options.go | 5 +++-- osscluster.go | 5 +++-- ring.go | 5 +++-- sentinel_test.go | 2 +- universal.go | 14 ++++++------- 12 files changed, 57 insertions(+), 22 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 515750af..5e0ac1d0 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v6.5.2 + uses: golangci/golangci-lint-action@v7.0.0 with: - verify: false # disable verifying the configuration since golangci is currently introducing breaking changes in the configuration + verify: true diff --git a/.golangci.yml b/.golangci.yml index 285aca6b..872454ff 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,3 +1,34 @@ +version: "2" run: timeout: 5m tests: false +linters: + settings: + staticcheck: + checks: + - all + # Incorrect or missing package comment. + # https://staticcheck.dev/docs/checks/#ST1000 + - -ST1000 + # Omit embedded fields from selector expression. + # https://staticcheck.dev/docs/checks/#QF1008 + - -QF1008 + - -ST1003 + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/command.go b/command.go index 364706e3..3253af6c 100644 --- a/command.go +++ b/command.go @@ -1412,7 +1412,8 @@ func (cmd *MapStringSliceInterfaceCmd) readReply(rd *proto.Reader) (err error) { cmd.val = make(map[string][]interface{}) - if readType == proto.RespMap { + switch readType { + case proto.RespMap: n, err := rd.ReadMapLen() if err != nil { return err @@ -1435,7 +1436,7 @@ func (cmd *MapStringSliceInterfaceCmd) readReply(rd *proto.Reader) (err error) { cmd.val[k][j] = value } } - } else if readType == proto.RespArray { + case proto.RespArray: // RESP2 response n, err := rd.ReadArrayLen() if err != nil { diff --git a/extra/rediscensus/go.mod b/extra/rediscensus/go.mod index 7033e805..b39f7dd4 100644 --- a/extra/rediscensus/go.mod +++ b/extra/rediscensus/go.mod @@ -19,6 +19,6 @@ require ( ) retract ( - v9.5.3 // This version was accidentally released. v9.7.2 // This version was accidentally released. + v9.5.3 // This version was accidentally released. ) diff --git a/extra/rediscmd/go.mod b/extra/rediscmd/go.mod index c1cff3e9..93cc423d 100644 --- a/extra/rediscmd/go.mod +++ b/extra/rediscmd/go.mod @@ -16,6 +16,6 @@ require ( ) retract ( - v9.5.3 // This version was accidentally released. v9.7.2 // This version was accidentally released. + v9.5.3 // This version was accidentally released. ) diff --git a/extra/redisotel/go.mod b/extra/redisotel/go.mod index e5b442e6..c5b29dff 100644 --- a/extra/redisotel/go.mod +++ b/extra/redisotel/go.mod @@ -24,6 +24,6 @@ require ( ) retract ( - v9.5.3 // This version was accidentally released. v9.7.2 // This version was accidentally released. + v9.5.3 // This version was accidentally released. ) diff --git a/extra/redisprometheus/go.mod b/extra/redisprometheus/go.mod index 8bff0008..c934767e 100644 --- a/extra/redisprometheus/go.mod +++ b/extra/redisprometheus/go.mod @@ -23,6 +23,6 @@ require ( ) retract ( - v9.5.3 // This version was accidentally released. v9.7.2 // This version was accidentally released. + v9.5.3 // This version was accidentally released. ) diff --git a/options.go b/options.go index 0ebeec34..3ffcd07e 100644 --- a/options.go +++ b/options.go @@ -214,9 +214,10 @@ func (opt *Options) init() { opt.ConnMaxIdleTime = 30 * time.Minute } - if opt.MaxRetries == -1 { + switch opt.MaxRetries { + case -1: opt.MaxRetries = 0 - } else if opt.MaxRetries == 0 { + case 0: opt.MaxRetries = 3 } switch opt.MinRetryBackoff { diff --git a/osscluster.go b/osscluster.go index 20180464..3b46cbe3 100644 --- a/osscluster.go +++ b/osscluster.go @@ -111,9 +111,10 @@ type ClusterOptions struct { } func (opt *ClusterOptions) init() { - if opt.MaxRedirects == -1 { + switch opt.MaxRedirects { + case -1: opt.MaxRedirects = 0 - } else if opt.MaxRedirects == 0 { + case 0: opt.MaxRedirects = 3 } diff --git a/ring.go b/ring.go index 0ff3f75b..8f2dd3c4 100644 --- a/ring.go +++ b/ring.go @@ -128,9 +128,10 @@ func (opt *RingOptions) init() { opt.NewConsistentHash = newRendezvous } - if opt.MaxRetries == -1 { + switch opt.MaxRetries { + case -1: opt.MaxRetries = 0 - } else if opt.MaxRetries == 0 { + case 0: opt.MaxRetries = 3 } switch opt.MinRetryBackoff { diff --git a/sentinel_test.go b/sentinel_test.go index cde7f956..2d481d5f 100644 --- a/sentinel_test.go +++ b/sentinel_test.go @@ -41,7 +41,7 @@ var _ = Describe("Sentinel resolution", func() { client := redis.NewFailoverClient(&redis.FailoverOptions{ MasterName: sentinelName, SentinelAddrs: sentinelAddrs, - MaxRetries: -1, + MaxRetries: -1, }) err := client.Ping(shortCtx).Err() diff --git a/universal.go b/universal.go index 3d91dd49..46d5640d 100644 --- a/universal.go +++ b/universal.go @@ -259,13 +259,13 @@ var ( // NewUniversalClient returns a new multi client. The type of the returned client depends // on the following conditions: // -// 1. If the MasterName option is specified with RouteByLatency, RouteRandomly or IsClusterMode, -// a FailoverClusterClient is returned. -// 2. If the MasterName option is specified without RouteByLatency, RouteRandomly or IsClusterMode, -// a sentinel-backed FailoverClient is returned. -// 3. If the number of Addrs is two or more, or IsClusterMode option is specified, -// a ClusterClient is returned. -// 4. Otherwise, a single-node Client is returned. +// 1. If the MasterName option is specified with RouteByLatency, RouteRandomly or IsClusterMode, +// a FailoverClusterClient is returned. +// 2. If the MasterName option is specified without RouteByLatency, RouteRandomly or IsClusterMode, +// a sentinel-backed FailoverClient is returned. +// 3. If the number of Addrs is two or more, or IsClusterMode option is specified, +// a ClusterClient is returned. +// 4. Otherwise, a single-node Client is returned. func NewUniversalClient(opts *UniversalOptions) UniversalClient { switch { case opts.MasterName != "" && (opts.RouteByLatency || opts.RouteRandomly || opts.IsClusterMode): From 683f644ec2d8bd82a3df9e9f62c9ee3e7d59373c Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov <1547186+ndyakov@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:08:34 +0300 Subject: [PATCH 4/4] chore(ci): Use redis 8 rc2 image. (#3361) * chore(ci): Use redis 8 rc2 image * test(timeseries): fix duplicatePolicy check --- .github/actions/run-tests/action.yml | 2 +- .github/workflows/build.yml | 6 +++--- timeseries_commands_test.go | 14 ++++++++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/actions/run-tests/action.yml b/.github/actions/run-tests/action.yml index 2edb16d3..08323aa5 100644 --- a/.github/actions/run-tests/action.yml +++ b/.github/actions/run-tests/action.yml @@ -25,7 +25,7 @@ runs: # Mapping of redis version to redis testing containers declare -A redis_version_mapping=( - ["8.0-RC1"]="8.0-RC1-pre" + ["8.0-RC2"]="8.0-RC2-pre" ["7.4.2"]="rs-7.4.0-v2" ["7.2.7"]="rs-7.2.0-v14" ) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f88ca672..810ab509 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: fail-fast: false matrix: redis-version: - - "8.0-RC1" # 8.0 RC1 + - "8.0-RC2" # 8.0 RC2 - "7.4.2" # should use redis stack 7.4 go-version: - "1.23.x" @@ -43,7 +43,7 @@ jobs: # Mapping of redis version to redis testing containers declare -A redis_version_mapping=( - ["8.0-RC1"]="8.0-RC1-pre" + ["8.0-RC2"]="8.0-RC2-pre" ["7.4.2"]="rs-7.4.0-v2" ) if [[ -v redis_version_mapping[$REDIS_VERSION] ]]; then @@ -72,7 +72,7 @@ jobs: fail-fast: false matrix: redis-version: - - "8.0-RC1" # 8.0 RC1 + - "8.0-RC2" # 8.0 RC2 - "7.4.2" # should use redis stack 7.4 - "7.2.7" # should redis stack 7.2 go-version: diff --git a/timeseries_commands_test.go b/timeseries_commands_test.go index d0d865b4..fdef3e60 100644 --- a/timeseries_commands_test.go +++ b/timeseries_commands_test.go @@ -269,11 +269,21 @@ var _ = Describe("RedisTimeseries commands", Label("timeseries"), func() { if client.Options().Protocol == 2 { Expect(resultInfo["labels"].([]interface{})[0]).To(BeEquivalentTo([]interface{}{"Time", "Series"})) Expect(resultInfo["retentionTime"]).To(BeEquivalentTo(10)) - Expect(resultInfo["duplicatePolicy"]).To(BeEquivalentTo(redis.Nil)) + if RedisVersion >= 8 { + Expect(resultInfo["duplicatePolicy"]).To(BeEquivalentTo("block")) + } else { + // Older versions of Redis had a bug where the duplicate policy was not set correctly + Expect(resultInfo["duplicatePolicy"]).To(BeEquivalentTo(redis.Nil)) + } } else { Expect(resultInfo["labels"].(map[interface{}]interface{})["Time"]).To(BeEquivalentTo("Series")) Expect(resultInfo["retentionTime"]).To(BeEquivalentTo(10)) - Expect(resultInfo["duplicatePolicy"]).To(BeEquivalentTo(redis.Nil)) + if RedisVersion >= 8 { + Expect(resultInfo["duplicatePolicy"]).To(BeEquivalentTo("block")) + } else { + // Older versions of Redis had a bug where the duplicate policy was not set correctly + Expect(resultInfo["duplicatePolicy"]).To(BeEquivalentTo(redis.Nil)) + } } opt = &redis.TSAlterOptions{DuplicatePolicy: "min"} resultAlter, err = client.TSAlter(ctx, "1", opt).Result()