From e1d723b313636a89e860bc1b922588b08ded2660 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Thu, 4 Dec 2025 15:36:20 +0200 Subject: [PATCH 1/9] chore(go): update go version to 1.21 --- .github/workflows/build.yml | 2 ++ README.md | 9 ++++----- example/cluster-mget/go.mod | 2 +- example/del-keys-without-ttl/go.mod | 2 +- example/digest-optimistic-locking/go.mod | 2 +- example/hll/go.mod | 2 +- example/hset-struct/go.mod | 2 +- example/lua-scripting/go.mod | 2 +- example/maintnotifiations-pubsub/go.mod | 2 +- example/redis-bloom/go.mod | 2 +- example/scan-struct/go.mod | 2 +- extra/rediscensus/go.mod | 2 +- extra/rediscmd/go.mod | 2 +- extra/redisotel/go.mod | 2 +- extra/redisprometheus/go.mod | 2 +- go.mod | 2 +- go.sum | 3 +++ internal/customvet/go.mod | 2 +- scripts/release.sh | 6 +++--- 19 files changed, 27 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b3fc813611..eb8524f81d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,6 +22,7 @@ jobs: - "8.2.x" # Redis CE 8.2 - "8.0.x" # Redis CE 8.0 go-version: + - "1.21.x" - "1.23.x" - "1.24.x" @@ -78,6 +79,7 @@ jobs: - "8.2.x" # Redis CE 8.2 - "8.0.x" # Redis CE 8.0 go-version: + - "1.21.x" - "1.23.x" - "1.24.x" diff --git a/README.md b/README.md index 38bd17b583..018d080255 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,12 @@ In `go-redis` we are aiming to support the last three releases of Redis. Current - [Redis 8.2](https://raw.githubusercontent.com/redis/redis/8.2/00-RELEASENOTES) - using Redis CE 8.2 - [Redis 8.4](https://raw.githubusercontent.com/redis/redis/8.4/00-RELEASENOTES) - using Redis CE 8.4 -Although the `go.mod` states it requires at minimum `go 1.18`, our CI is configured to run the tests against all three -versions of Redis and latest two versions of Go ([1.23](https://go.dev/doc/devel/release#go1.23.0), -[1.24](https://go.dev/doc/devel/release#go1.24.0)). We observe that some modules related test may not pass with +Although the `go.mod` states it requires at minimum `go 1.21`, our CI is configured to run the tests against all three +versions of Redis and multiple versions of Go ([1.21](https://go.dev/doc/devel/release#go1.21.0), +[1.23](https://go.dev/doc/devel/release#go1.23.0), [1.24](https://go.dev/doc/devel/release#go1.24.0)). We observe that some modules related test may not pass with Redis Stack 7.2 and some commands are changed with Redis CE 8.0. Although it is not officially supported, `go-redis/v9` should be able to work with any Redis 7.0+. -Please do refer to the documentation and the tests if you experience any issues. We do plan to update the go version -in the `go.mod` to `go 1.24` in one of the next releases. +Please do refer to the documentation and the tests if you experience any issues. ## How do I Redis? diff --git a/example/cluster-mget/go.mod b/example/cluster-mget/go.mod index 77b47b30da..334a1aabb0 100644 --- a/example/cluster-mget/go.mod +++ b/example/cluster-mget/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/example/cluster-mget -go 1.18 +go 1.21 replace github.com/redis/go-redis/v9 => ../.. diff --git a/example/del-keys-without-ttl/go.mod b/example/del-keys-without-ttl/go.mod index 8b5c517cff..a0f07a5387 100644 --- a/example/del-keys-without-ttl/go.mod +++ b/example/del-keys-without-ttl/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/example/del-keys-without-ttl -go 1.18 +go 1.21 replace github.com/redis/go-redis/v9 => ../.. diff --git a/example/digest-optimistic-locking/go.mod b/example/digest-optimistic-locking/go.mod index 4a93e7d3b9..0d7f9850b4 100644 --- a/example/digest-optimistic-locking/go.mod +++ b/example/digest-optimistic-locking/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/example/digest-optimistic-locking -go 1.18 +go 1.21 replace github.com/redis/go-redis/v9 => ../.. diff --git a/example/hll/go.mod b/example/hll/go.mod index def62dc756..fb6b41fd11 100644 --- a/example/hll/go.mod +++ b/example/hll/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/example/hll -go 1.18 +go 1.21 replace github.com/redis/go-redis/v9 => ../.. diff --git a/example/hset-struct/go.mod b/example/hset-struct/go.mod index 3f10b4be9f..b4a83a4c38 100644 --- a/example/hset-struct/go.mod +++ b/example/hset-struct/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/example/scan-struct -go 1.18 +go 1.21 replace github.com/redis/go-redis/v9 => ../.. diff --git a/example/lua-scripting/go.mod b/example/lua-scripting/go.mod index f1f46577d4..585091f560 100644 --- a/example/lua-scripting/go.mod +++ b/example/lua-scripting/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/example/lua-scripting -go 1.18 +go 1.21 replace github.com/redis/go-redis/v9 => ../.. diff --git a/example/maintnotifiations-pubsub/go.mod b/example/maintnotifiations-pubsub/go.mod index 9fabdb991e..d402dc0c98 100644 --- a/example/maintnotifiations-pubsub/go.mod +++ b/example/maintnotifiations-pubsub/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/example/pubsub -go 1.18 +go 1.21 replace github.com/redis/go-redis/v9 => ../.. diff --git a/example/redis-bloom/go.mod b/example/redis-bloom/go.mod index c52ca9814a..6a1ef50ba0 100644 --- a/example/redis-bloom/go.mod +++ b/example/redis-bloom/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/example/redis-bloom -go 1.18 +go 1.21 replace github.com/redis/go-redis/v9 => ../.. diff --git a/example/scan-struct/go.mod b/example/scan-struct/go.mod index 3f10b4be9f..b4a83a4c38 100644 --- a/example/scan-struct/go.mod +++ b/example/scan-struct/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/example/scan-struct -go 1.18 +go 1.21 replace github.com/redis/go-redis/v9 => ../.. diff --git a/extra/rediscensus/go.mod b/extra/rediscensus/go.mod index 95dfc0ff8d..3cb2a108db 100644 --- a/extra/rediscensus/go.mod +++ b/extra/rediscensus/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/extra/rediscensus/v9 -go 1.19 +go 1.21 replace github.com/redis/go-redis/v9 => ../.. diff --git a/extra/rediscmd/go.mod b/extra/rediscmd/go.mod index 3557b016f9..d8d8405d19 100644 --- a/extra/rediscmd/go.mod +++ b/extra/rediscmd/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/extra/rediscmd/v9 -go 1.19 +go 1.21 replace github.com/redis/go-redis/v9 => ../.. diff --git a/extra/redisotel/go.mod b/extra/redisotel/go.mod index 56766fa07f..3ebcca1dd0 100644 --- a/extra/redisotel/go.mod +++ b/extra/redisotel/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/extra/redisotel/v9 -go 1.19 +go 1.21 replace github.com/redis/go-redis/v9 => ../.. diff --git a/extra/redisprometheus/go.mod b/extra/redisprometheus/go.mod index f92734cc70..1b558bf264 100644 --- a/extra/redisprometheus/go.mod +++ b/extra/redisprometheus/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/extra/redisprometheus/v9 -go 1.19 +go 1.21 replace github.com/redis/go-redis/v9 => ../.. diff --git a/go.mod b/go.mod index 0d3144f695..854e21323f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/v9 -go 1.18 +go 1.21 require ( github.com/bsm/ginkgo/v2 v2.12.0 diff --git a/go.sum b/go.sum index ab06e043de..4ac0b36ede 100644 --- a/go.sum +++ b/go.sum @@ -5,9 +5,12 @@ github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0 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= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= diff --git a/internal/customvet/go.mod b/internal/customvet/go.mod index edaa2d7bac..202fd404da 100644 --- a/internal/customvet/go.mod +++ b/internal/customvet/go.mod @@ -1,6 +1,6 @@ module github.com/redis/go-redis/internal/customvet -go 1.17 +go 1.21 require golang.org/x/tools v0.5.0 diff --git a/scripts/release.sh b/scripts/release.sh index fb7d58788c..e18db78e85 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -48,15 +48,15 @@ PACKAGE_DIRS=$(find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; \ for dir in $PACKAGE_DIRS do printf "${dir}: go get -u && go mod tidy\n" - #(cd ./${dir} && go get -u && go mod tidy -compat=1.18) + #(cd ./${dir} && go get -u && go mod tidy -compat=1.21) done for dir in $PACKAGE_DIRS do sed --in-place \ "s/redis\/go-redis\([^ ]*\) v.*/redis\/go-redis\1 ${TAG}/" "${dir}/go.mod" - #(cd ./${dir} && go get -u && go mod tidy -compat=1.18) - (cd ./${dir} && go mod tidy -compat=1.18) + #(cd ./${dir} && go get -u && go mod tidy -compat=1.21) + (cd ./${dir} && go mod tidy -compat=1.21) done sed --in-place "s/\(return \)\"[^\"]*\"/\1\"${TAG#v}\"/" ./version.go From 3baf681186fe4cadd95661de655039e5ad9030e2 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Thu, 4 Dec 2025 15:45:34 +0200 Subject: [PATCH 2/9] chore(aggregators): make aggregators work with 1.21 --- internal/routing/aggregator.go | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/internal/routing/aggregator.go b/internal/routing/aggregator.go index db9860007b..41ffe9b857 100644 --- a/internal/routing/aggregator.go +++ b/internal/routing/aggregator.go @@ -482,10 +482,20 @@ func (a *AggLogicalAndAggregator) Add(result interface{}, err error) error { return e } + // Atomic AND operation using CompareAndSwap loop (Go 1.21 compatible) + // TODO: Once minimum Go version is upgraded to 1.23+, replace this with: + // if val { a.res.And(1) } else { a.res.And(0) } + var newVal int64 if val { - a.res.And(1) + newVal = 1 } else { - a.res.And(0) + newVal = 0 + } + for { + old := a.res.Load() + if a.res.CompareAndSwap(old, old&newVal) { + break + } } a.hasResult.Store(true) @@ -566,10 +576,20 @@ func (a *AggLogicalOrAggregator) Add(result interface{}, err error) error { return e } + // Atomic OR operation using CompareAndSwap loop (Go 1.21 compatible) + // TODO: Once minimum Go version is upgraded to 1.23+, replace this with: + // if val { a.res.Or(1) } else { a.res.Or(0) } + var newVal int64 if val { - a.res.Or(1) + newVal = 1 } else { - a.res.Or(0) + newVal = 0 + } + for { + old := a.res.Load() + if a.res.CompareAndSwap(old, old|newVal) { + break + } } a.hasResult.Store(true) From 259997856931c1ffcaa51fa943fc821c9ad21150 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Thu, 4 Dec 2025 17:24:39 +0200 Subject: [PATCH 3/9] fix doctests --- doctests/timeseries_tut_test.go | 24 ++++++++++++++++-------- internal/routing/aggregator.go | 14 ++++++++++++-- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/doctests/timeseries_tut_test.go b/doctests/timeseries_tut_test.go index 28e1635399..a77583dd07 100644 --- a/doctests/timeseries_tut_test.go +++ b/doctests/timeseries_tut_test.go @@ -5,9 +5,7 @@ package example_commands_test import ( "context" "fmt" - "maps" "math" - "slices" "sort" "github.com/redis/go-redis/v9" @@ -15,6 +13,16 @@ import ( // HIDE_END +// mapKeys returns a slice of all keys from the map (Go 1.21 compatible) +// TODO: Once minimum Go version is upgraded to 1.23+, replace with slices.Collect(maps.Keys(m)) +func mapKeys[K comparable, V any](m map[K]V) []K { + keys := make([]K, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + return keys +} + func ExampleClient_timeseries_create() { ctx := context.Background() @@ -417,7 +425,7 @@ func ExampleClient_timeseries_query_multi() { panic(err) } - res28Keys := slices.Collect(maps.Keys(res28)) + res28Keys := mapKeys(res28) sort.Strings(res28Keys) for _, k := range res28Keys { @@ -457,7 +465,7 @@ func ExampleClient_timeseries_query_multi() { panic(err) } - res29Keys := slices.Collect(maps.Keys(res29)) + res29Keys := mapKeys(res29) sort.Strings(res29Keys) for _, k := range res29Keys { @@ -505,7 +513,7 @@ func ExampleClient_timeseries_query_multi() { panic(err) } - res30Keys := slices.Collect(maps.Keys(res30)) + res30Keys := mapKeys(res30) sort.Strings(res30Keys) for _, k := range res30Keys { @@ -550,7 +558,7 @@ func ExampleClient_timeseries_query_multi() { panic(err) } - res31Keys := slices.Collect(maps.Keys(res31)) + res31Keys := mapKeys(res31) sort.Strings(res31Keys) for _, k := range res31Keys { @@ -857,7 +865,7 @@ func ExampleClient_timeseries_aggmulti() { panic(err) } - res44Keys := slices.Collect(maps.Keys(res44)) + res44Keys := mapKeys(res44) sort.Strings(res44Keys) for _, k := range res44Keys { @@ -905,7 +913,7 @@ func ExampleClient_timeseries_aggmulti() { panic(err) } - res45Keys := slices.Collect(maps.Keys(res45)) + res45Keys := mapKeys(res45) sort.Strings(res45Keys) for _, k := range res45Keys { diff --git a/internal/routing/aggregator.go b/internal/routing/aggregator.go index 41ffe9b857..04a772d44d 100644 --- a/internal/routing/aggregator.go +++ b/internal/routing/aggregator.go @@ -493,7 +493,12 @@ func (a *AggLogicalAndAggregator) Add(result interface{}, err error) error { } for { old := a.res.Load() - if a.res.CompareAndSwap(old, old&newVal) { + desired := old & newVal + if a.res.CompareAndSwap(old, desired) { + break + } + // Early exit: if the value already equals what we want, no need to continue + if a.res.Load() == desired { break } } @@ -587,7 +592,12 @@ func (a *AggLogicalOrAggregator) Add(result interface{}, err error) error { } for { old := a.res.Load() - if a.res.CompareAndSwap(old, old|newVal) { + desired := old | newVal + if a.res.CompareAndSwap(old, desired) { + break + } + // Early exit: if the value already equals what we want, no need to continue + if a.res.Load() == desired { break } } From 3f44a7fd0d8b2eb723561eb0ef34dc33e30493a8 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Thu, 4 Dec 2025 18:34:53 +0200 Subject: [PATCH 4/9] address copilot comments --- internal/routing/aggregator.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/internal/routing/aggregator.go b/internal/routing/aggregator.go index 04a772d44d..fd757b847f 100644 --- a/internal/routing/aggregator.go +++ b/internal/routing/aggregator.go @@ -497,10 +497,6 @@ func (a *AggLogicalAndAggregator) Add(result interface{}, err error) error { if a.res.CompareAndSwap(old, desired) { break } - // Early exit: if the value already equals what we want, no need to continue - if a.res.Load() == desired { - break - } } a.hasResult.Store(true) @@ -596,10 +592,6 @@ func (a *AggLogicalOrAggregator) Add(result interface{}, err error) error { if a.res.CompareAndSwap(old, desired) { break } - // Early exit: if the value already equals what we want, no need to continue - if a.res.Load() == desired { - break - } } a.hasResult.Store(true) From 9285eacd2ef8ae8ab79708e07a3451096e96eee3 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Thu, 4 Dec 2025 22:03:52 +0200 Subject: [PATCH 5/9] use atomic bool for logic and/or aggregators --- internal/routing/aggregator.go | 44 +---- internal/routing/aggregator_test.go | 271 ++++++++++++++++++++++++++++ 2 files changed, 281 insertions(+), 34 deletions(-) create mode 100644 internal/routing/aggregator_test.go diff --git a/internal/routing/aggregator.go b/internal/routing/aggregator.go index fd757b847f..0d6321ec11 100644 --- a/internal/routing/aggregator.go +++ b/internal/routing/aggregator.go @@ -65,7 +65,7 @@ func NewResponseAggregator(policy ResponsePolicy, cmdName string) ResponseAggreg } case RespAggLogicalAnd: andAgg := &AggLogicalAndAggregator{} - andAgg.res.Add(1) + andAgg.res.Store(true) return andAgg case RespAggLogicalOr: @@ -466,7 +466,7 @@ func (a *AggMaxAggregator) Result() (interface{}, error) { // AggLogicalAndAggregator performs logical AND on boolean values. type AggLogicalAndAggregator struct { err atomic.Value - res atomic.Int64 + res atomic.Bool hasResult atomic.Bool } @@ -482,21 +482,9 @@ func (a *AggLogicalAndAggregator) Add(result interface{}, err error) error { return e } - // Atomic AND operation using CompareAndSwap loop (Go 1.21 compatible) - // TODO: Once minimum Go version is upgraded to 1.23+, replace this with: - // if val { a.res.And(1) } else { a.res.And(0) } - var newVal int64 - if val { - newVal = 1 - } else { - newVal = 0 - } - for { - old := a.res.Load() - desired := old & newVal - if a.res.CompareAndSwap(old, desired) { - break - } + // Atomic AND operation: if val is false, result is always false + if !val { + a.res.Store(false) } a.hasResult.Store(true) @@ -555,13 +543,13 @@ func (a *AggLogicalAndAggregator) Result() (interface{}, error) { if !a.hasResult.Load() { return nil, ErrAndAggregation } - return a.res.Load() != 0, nil + return a.res.Load(), nil } // AggLogicalOrAggregator performs logical OR on boolean values. type AggLogicalOrAggregator struct { err atomic.Value - res atomic.Int64 + res atomic.Bool hasResult atomic.Bool } @@ -577,21 +565,9 @@ func (a *AggLogicalOrAggregator) Add(result interface{}, err error) error { return e } - // Atomic OR operation using CompareAndSwap loop (Go 1.21 compatible) - // TODO: Once minimum Go version is upgraded to 1.23+, replace this with: - // if val { a.res.Or(1) } else { a.res.Or(0) } - var newVal int64 + // Atomic OR operation: if val is true, result is always true if val { - newVal = 1 - } else { - newVal = 0 - } - for { - old := a.res.Load() - desired := old | newVal - if a.res.CompareAndSwap(old, desired) { - break - } + a.res.Store(true) } a.hasResult.Store(true) @@ -650,7 +626,7 @@ func (a *AggLogicalOrAggregator) Result() (interface{}, error) { if !a.hasResult.Load() { return nil, ErrOrAggregation } - return a.res.Load() != 0, nil + return a.res.Load(), nil } func toInt64(val interface{}) (int64, error) { diff --git a/internal/routing/aggregator_test.go b/internal/routing/aggregator_test.go new file mode 100644 index 0000000000..11e92d8c8f --- /dev/null +++ b/internal/routing/aggregator_test.go @@ -0,0 +1,271 @@ +package routing + +import ( + "errors" + "testing" +) + +func TestAggLogicalAndAggregator(t *testing.T) { + t.Run("all true values", func(t *testing.T) { + agg := NewResponseAggregator(RespAggLogicalAnd, "") + + err := agg.Add(true, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + err = agg.Add(int64(1), nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + err = agg.Add(1, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + result, err := agg.Result() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if result != true { + t.Errorf("expected true, got %v", result) + } + }) + + t.Run("one false value", func(t *testing.T) { + agg := NewResponseAggregator(RespAggLogicalAnd, "") + + err := agg.Add(true, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + err = agg.Add(false, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + err = agg.Add(true, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + result, err := agg.Result() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if result != false { + t.Errorf("expected false, got %v", result) + } + }) + + t.Run("no results", func(t *testing.T) { + agg := NewResponseAggregator(RespAggLogicalAnd, "") + + _, err := agg.Result() + if err != ErrAndAggregation { + t.Errorf("expected ErrAndAggregation, got %v", err) + } + }) + + t.Run("with error", func(t *testing.T) { + agg := NewResponseAggregator(RespAggLogicalAnd, "") + + testErr := errors.New("test error") + err := agg.Add(nil, testErr) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + _, err = agg.Result() + if err != testErr { + t.Errorf("expected test error, got %v", err) + } + }) +} + +func TestAggLogicalOrAggregator(t *testing.T) { + t.Run("all false values", func(t *testing.T) { + agg := NewResponseAggregator(RespAggLogicalOr, "") + + err := agg.Add(false, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + err = agg.Add(int64(0), nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + err = agg.Add(0, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + result, err := agg.Result() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if result != false { + t.Errorf("expected false, got %v", result) + } + }) + + t.Run("one true value", func(t *testing.T) { + agg := NewResponseAggregator(RespAggLogicalOr, "") + + err := agg.Add(false, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + err = agg.Add(true, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + err = agg.Add(false, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + result, err := agg.Result() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if result != true { + t.Errorf("expected true, got %v", result) + } + }) + + t.Run("no results", func(t *testing.T) { + agg := NewResponseAggregator(RespAggLogicalOr, "") + + _, err := agg.Result() + if err != ErrOrAggregation { + t.Errorf("expected ErrOrAggregation, got %v", err) + } + }) + + t.Run("with error", func(t *testing.T) { + agg := NewResponseAggregator(RespAggLogicalOr, "") + + testErr := errors.New("test error") + err := agg.Add(nil, testErr) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + _, err = agg.Result() + if err != testErr { + t.Errorf("expected test error, got %v", err) + } + }) +} + +func TestAggLogicalAndBatchAdd(t *testing.T) { + t.Run("batch add all true", func(t *testing.T) { + agg := NewResponseAggregator(RespAggLogicalAnd, "") + + results := map[string]AggregatorResErr{ + "key1": {Result: true, Err: nil}, + "key2": {Result: int64(1), Err: nil}, + "key3": {Result: 1, Err: nil}, + } + + err := agg.BatchAdd(results) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + result, err := agg.Result() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if result != true { + t.Errorf("expected true, got %v", result) + } + }) + + t.Run("batch add with false", func(t *testing.T) { + agg := NewResponseAggregator(RespAggLogicalAnd, "") + + results := map[string]AggregatorResErr{ + "key1": {Result: true, Err: nil}, + "key2": {Result: false, Err: nil}, + "key3": {Result: true, Err: nil}, + } + + err := agg.BatchAdd(results) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + result, err := agg.Result() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if result != false { + t.Errorf("expected false, got %v", result) + } + }) +} + +func TestAggLogicalOrBatchAdd(t *testing.T) { + t.Run("batch add all false", func(t *testing.T) { + agg := NewResponseAggregator(RespAggLogicalOr, "") + + results := map[string]AggregatorResErr{ + "key1": {Result: false, Err: nil}, + "key2": {Result: int64(0), Err: nil}, + "key3": {Result: 0, Err: nil}, + } + + err := agg.BatchAdd(results) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + result, err := agg.Result() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if result != false { + t.Errorf("expected false, got %v", result) + } + }) + + t.Run("batch add with true", func(t *testing.T) { + agg := NewResponseAggregator(RespAggLogicalOr, "") + + results := map[string]AggregatorResErr{ + "key1": {Result: false, Err: nil}, + "key2": {Result: true, Err: nil}, + "key3": {Result: false, Err: nil}, + } + + err := agg.BatchAdd(results) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + result, err := agg.Result() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if result != true { + t.Errorf("expected true, got %v", result) + } + }) +} + From 3135b651bebc37adee17437cf1351dbfade8730f Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov <1547186+ndyakov@users.noreply.github.com> Date: Thu, 4 Dec 2025 22:07:23 +0200 Subject: [PATCH 6/9] Update .github/workflows/build.yml Co-authored-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com> --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eb8524f81d..ea6784d85f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,8 @@ jobs: go-version: - "1.21.x" - "1.23.x" - - "1.24.x" + - oldstable + - stable steps: - name: Set up ${{ matrix.go-version }} From 2f08fd1a683836ea3753968d6f92ac1345627425 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Thu, 4 Dec 2025 22:12:00 +0200 Subject: [PATCH 7/9] use stable/oldstable, 1.23 and 1.21 --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ea6784d85f..b104b1819f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,7 +82,8 @@ jobs: go-version: - "1.21.x" - "1.23.x" - - "1.24.x" + - oldstable + - stable steps: - name: Checkout code From a6e77505b6935b510969265edd5578406a529b79 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Fri, 5 Dec 2025 11:28:02 +0200 Subject: [PATCH 8/9] fix versions in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 018d080255..54dd0794b1 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ In `go-redis` we are aiming to support the last three releases of Redis. Current Although the `go.mod` states it requires at minimum `go 1.21`, our CI is configured to run the tests against all three versions of Redis and multiple versions of Go ([1.21](https://go.dev/doc/devel/release#go1.21.0), -[1.23](https://go.dev/doc/devel/release#go1.23.0), [1.24](https://go.dev/doc/devel/release#go1.24.0)). We observe that some modules related test may not pass with +[1.23](https://go.dev/doc/devel/release#go1.23.0), oldstable, and stable). We observe that some modules related test may not pass with Redis Stack 7.2 and some commands are changed with Redis CE 8.0. Although it is not officially supported, `go-redis/v9` should be able to work with any Redis 7.0+. Please do refer to the documentation and the tests if you experience any issues. From 276f8722ffbf0a60122e6cb3f2b67888156b5b90 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Fri, 5 Dec 2025 11:38:49 +0200 Subject: [PATCH 9/9] add oldstable in wordlist --- .github/wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/wordlist.txt b/.github/wordlist.txt index 741c51aa03..6c9223b34e 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -76,3 +76,4 @@ oauth entraid MiB KiB +oldstable