diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 403e199d0..231233f79 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- go-version: [1.19.x, 1.20.x]
+ go-version: [1.19.x, 1.20.x, 1.21.x]
services:
redis:
@@ -33,7 +33,7 @@ jobs:
go-version: ${{ matrix.go-version }}
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Test
run: make test
diff --git a/.github/workflows/doctests.yaml b/.github/workflows/doctests.yaml
index 00b0063bf..708dfcd3f 100644
--- a/.github/workflows/doctests.yaml
+++ b/.github/workflows/doctests.yaml
@@ -25,7 +25,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- go-version: [ "1.18", "1.19", "1.20" ]
+ go-version: [ "1.18", "1.19", "1.20", "1.21" ]
steps:
- name: Set up ${{ matrix.go-version }}
@@ -34,8 +34,8 @@ jobs:
go-version: ${{ matrix.go-version }}
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Test doc examples
working-directory: ./doctests
- run: go test
\ No newline at end of file
+ run: go test
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index d3232ecbd..e35ee85c0 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -21,6 +21,6 @@ jobs:
name: lint
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml
index e15284155..3bd776cf1 100644
--- a/.github/workflows/spellcheck.yml
+++ b/.github/workflows/spellcheck.yml
@@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Check Spelling
uses: rojopolis/spellcheck-github-actions@0.33.1
with:
diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml
new file mode 100644
index 000000000..32fd9e817
--- /dev/null
+++ b/.github/workflows/stale-issues.yml
@@ -0,0 +1,25 @@
+name: "Close stale issues"
+on:
+ schedule:
+ - cron: "0 0 * * *"
+
+permissions: {}
+jobs:
+ stale:
+ permissions:
+ issues: write # to close stale issues (actions/stale)
+ pull-requests: write # to close stale PRs (actions/stale)
+
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v3
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ stale-issue-message: 'This issue is marked stale. It will be closed in 30 days if it is not updated.'
+ stale-pr-message: 'This pull request is marked stale. It will be closed in 30 days if it is not updated.'
+ days-before-stale: 365
+ days-before-close: 30
+ stale-issue-label: "Stale"
+ stale-pr-label: "Stale"
+ operations-per-run: 10
+ remove-stale-when-updated: true
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..90030b89f
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,101 @@
+# Contributing
+
+## Introduction
+
+We appreciate your interest in considering contributing to go-redis.
+Community contributions mean a lot to us.
+
+## Contributions we need
+
+You may already know how you'd like to contribute, whether it's a fix for a bug you
+encountered, or a new feature your team wants to use.
+
+If you don't know where to start, consider improving
+documentation, bug triaging, and writing tutorials are all examples of
+helpful contributions that mean less work for you.
+
+## Your First Contribution
+
+Unsure where to begin contributing? You can start by looking through
+[help-wanted
+issues](https://github.com/redis/go-redis/issues?q=is%3Aopen+is%3Aissue+label%3ahelp-wanted).
+
+Never contributed to open source before? Here are a couple of friendly
+tutorials:
+
+-
+-
+
+## Getting Started
+
+Here's how to get started with your code contribution:
+
+1. Create your own fork of go-redis
+2. Do the changes in your fork
+3. If you need a development environment, run `make test`. Note: this clones and builds the latest release of [redis](https://redis.io). You also need a redis-stack-server docker, in order to run the capabilities tests. This can be started by running:
+ ```docker run -p 6379:6379 -it redis/redis-stack-server:edge```
+4. While developing, make sure the tests pass by running `make tests`
+5. If you like the change and think the project could use it, send a
+ pull request
+
+To see what else is part of the automation, run `invoke -l`
+
+## Testing
+
+Call `make test` to run all tests, including linters.
+
+Continuous Integration uses these same wrappers to run all of these
+tests against multiple versions of python. Feel free to test your
+changes against all the go versions supported, as declared by the
+[build.yml](./.github/workflows/build.yml) file.
+
+### Troubleshooting
+
+If you get any errors when running `make test`, make sure
+that you are using supported versions of Docker and go.
+
+## How to Report a Bug
+
+### Security Vulnerabilities
+
+**NOTE**: If you find a security vulnerability, do NOT open an issue.
+Email [Redis Open Source ()](mailto:oss@redis.com) instead.
+
+In order to determine whether you are dealing with a security issue, ask
+yourself these two questions:
+
+- Can I access something that's not mine, or something I shouldn't
+ have access to?
+- Can I disable something for other people?
+
+If the answer to either of those two questions are *yes*, then you're
+probably dealing with a security issue. Note that even if you answer
+*no* to both questions, you may still be dealing with a security
+issue, so if you're unsure, just email [us](mailto:oss@redis.com).
+
+### Everything Else
+
+When filing an issue, make sure to answer these five questions:
+
+1. What version of go-redis are you using?
+2. What version of redis are you using?
+3. What did you do?
+4. What did you expect to see?
+5. What did you see instead?
+
+## Suggest a feature or enhancement
+
+If you'd like to contribute a new feature, make sure you check our
+issue list to see if someone has already proposed it. Work may already
+be underway on the feature you want or we may have rejected a
+feature like it already.
+
+If you don't see anything, open a new issue that describes the feature
+you would like and how it should work.
+
+## Code review process
+
+The core team regularly looks at pull requests. We will provide
+feedback as soon as possible. After receiving our feedback, please respond
+within two weeks. After that time, we may close your PR if it isn't
+showing any activity.
diff --git a/Makefile b/Makefile
index b59c39554..cf1f6428d 100644
--- a/Makefile
+++ b/Makefile
@@ -23,7 +23,7 @@ bench: testdeps
testdata/redis:
mkdir -p $@
- wget -qO- https://download.redis.io/releases/redis-7.2-rc3.tar.gz | tar xvz --strip-components=1 -C $@
+ wget -qO- https://download.redis.io/releases/redis-7.2.1.tar.gz | tar xvz --strip-components=1 -C $@
testdata/redis/src/redis-server: testdata/redis
cd $< && make all
diff --git a/README.md b/README.md
index 3486e8e5a..1005868c0 100644
--- a/README.md
+++ b/README.md
@@ -140,6 +140,10 @@ func ExampleClient() {
rdb := redis.NewClient(opts)
```
+## Contributing
+
+Please see [out contributing guidelines](CONTRIBUTING.md) to help us improve this library!
+
## Look and feel
Some corner cases:
diff --git a/bench_decode_test.go b/bench_decode_test.go
index de53064f2..50e339086 100644
--- a/bench_decode_test.go
+++ b/bench_decode_test.go
@@ -30,6 +30,7 @@ func NewClientStub(resp []byte) *ClientStub {
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
return stub.stubConn(initHello), nil
},
+ DisableIndentity: true,
})
return stub
}
@@ -45,6 +46,8 @@ func NewClusterClientStub(resp []byte) *ClientStub {
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
return stub.stubConn(initHello), nil
},
+ DisableIndentity: true,
+
ClusterSlots: func(_ context.Context) ([]ClusterSlot, error) {
return []ClusterSlot{
{
diff --git a/cluster.go b/cluster.go
index 941838dd0..b30e22637 100644
--- a/cluster.go
+++ b/cluster.go
@@ -83,7 +83,8 @@ type ClusterOptions struct {
ConnMaxIdleTime time.Duration
ConnMaxLifetime time.Duration
- TLSConfig *tls.Config
+ TLSConfig *tls.Config
+ DisableIndentity bool // Disable set-lib on connect. Default is false.
}
func (opt *ClusterOptions) init() {
@@ -277,15 +278,15 @@ func (opt *ClusterOptions) clientOptions() *Options {
ReadTimeout: opt.ReadTimeout,
WriteTimeout: opt.WriteTimeout,
- PoolFIFO: opt.PoolFIFO,
- PoolSize: opt.PoolSize,
- PoolTimeout: opt.PoolTimeout,
- MinIdleConns: opt.MinIdleConns,
- MaxIdleConns: opt.MaxIdleConns,
- ConnMaxIdleTime: opt.ConnMaxIdleTime,
- ConnMaxLifetime: opt.ConnMaxLifetime,
-
- TLSConfig: opt.TLSConfig,
+ PoolFIFO: opt.PoolFIFO,
+ PoolSize: opt.PoolSize,
+ PoolTimeout: opt.PoolTimeout,
+ MinIdleConns: opt.MinIdleConns,
+ MaxIdleConns: opt.MaxIdleConns,
+ ConnMaxIdleTime: opt.ConnMaxIdleTime,
+ ConnMaxLifetime: opt.ConnMaxLifetime,
+ DisableIndentity: opt.DisableIndentity,
+ TLSConfig: opt.TLSConfig,
// If ClusterSlots is populated, then we probably have an artificial
// cluster whose nodes are not in clustering mode (otherwise there isn't
// much use for ClusterSlots config). This means we cannot execute the
diff --git a/commands.go b/commands.go
index 2bb035cec..0c1f985e6 100644
--- a/commands.go
+++ b/commands.go
@@ -4,9 +4,11 @@ import (
"context"
"encoding"
"errors"
+ "fmt"
"io"
"net"
"reflect"
+ "runtime"
"strings"
"time"
@@ -236,7 +238,7 @@ type Cmdable interface {
BitOpNot(ctx context.Context, destKey string, key string) *IntCmd
BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd
BitPosSpan(ctx context.Context, key string, bit int8, start, end int64, span string) *IntCmd
- BitField(ctx context.Context, key string, args ...interface{}) *IntSliceCmd
+ BitField(ctx context.Context, key string, values ...interface{}) *IntSliceCmd
Scan(ctx context.Context, cursor uint64, match string, count int64) *ScanCmd
ScanType(ctx context.Context, cursor uint64, match string, count int64, keyType string) *ScanCmd
@@ -584,7 +586,8 @@ func (c statefulCmdable) ClientSetInfo(ctx context.Context, info LibraryInfo) *S
var cmd *StatusCmd
if info.LibName != nil {
- cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-NAME", *info.LibName)
+ libName := fmt.Sprintf("go-redis(%s,%s)", *info.LibName, runtime.Version())
+ cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-NAME", libName)
} else {
cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-VER", *info.LibVer)
}
@@ -1366,12 +1369,16 @@ func (c cmdable) BitPosSpan(ctx context.Context, key string, bit int8, start, en
return cmd
}
-func (c cmdable) BitField(ctx context.Context, key string, args ...interface{}) *IntSliceCmd {
- a := make([]interface{}, 0, 2+len(args))
- a = append(a, "bitfield")
- a = append(a, key)
- a = append(a, args...)
- cmd := NewIntSliceCmd(ctx, a...)
+// BitField accepts multiple values:
+// - BitField("set", "i1", "offset1", "value1","cmd2", "type2", "offset2", "value2")
+// - BitField([]string{"cmd1", "type1", "offset1", "value1","cmd2", "type2", "offset2", "value2"})
+// - BitField([]interface{}{"cmd1", "type1", "offset1", "value1","cmd2", "type2", "offset2", "value2"})
+func (c cmdable) BitField(ctx context.Context, key string, values ...interface{}) *IntSliceCmd {
+ args := make([]interface{}, 2, 2+len(values))
+ args[0] = "bitfield"
+ args[1] = key
+ args = appendArgs(args, values)
+ cmd := NewIntSliceCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
diff --git a/commands_test.go b/commands_test.go
index c8ea0f7bd..9b1c63e81 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -1253,6 +1253,10 @@ var _ = Describe("Commands", func() {
nn, err := client.BitField(ctx, "mykey", "INCRBY", "i5", 100, 1, "GET", "u4", 0).Result()
Expect(err).NotTo(HaveOccurred())
Expect(nn).To(Equal([]int64{1, 0}))
+
+ nn, err = client.BitField(ctx, "mykey", "set", "i1", 1, 1, "GET", "u4", 0).Result()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(nn).To(Equal([]int64{0, 4}))
})
It("should Decr", func() {
@@ -2052,10 +2056,9 @@ var _ = Describe("Commands", func() {
logEntries, err := client.ACLLog(ctx, 10).Result()
Expect(err).NotTo(HaveOccurred())
- Expect(len(logEntries)).To(Equal(3))
+ Expect(len(logEntries)).To(Equal(4))
for _, entry := range logEntries {
- Expect(entry.Count).To(BeNumerically("==", 1))
Expect(entry.Reason).To(Equal("command"))
Expect(entry.Context).To(Equal("toplevel"))
Expect(entry.Object).NotTo(BeEmpty())
diff --git a/options.go b/options.go
index bb4816b27..f10bad38b 100644
--- a/options.go
+++ b/options.go
@@ -136,6 +136,9 @@ type Options struct {
// Enables read only queries on slave/follower nodes.
readOnly bool
+
+ // // Disable set-lib on connect. Default is false.
+ DisableIndentity bool
}
func (opt *Options) init() {
diff --git a/probabilistic.go b/probabilistic.go
index df351bfda..09dbda32b 100644
--- a/probabilistic.go
+++ b/probabilistic.go
@@ -24,7 +24,7 @@ type ProbabilisticCmdable interface {
BFReserve(ctx context.Context, key string, errorRate float64, capacity int64) *StatusCmd
BFReserveExpansion(ctx context.Context, key string, errorRate float64, capacity, expansion int64) *StatusCmd
BFReserveNonScaling(ctx context.Context, key string, errorRate float64, capacity int64) *StatusCmd
- BFReserveArgs(ctx context.Context, key string, options *BFReserveOptions) *StatusCmd
+ BFReserveWithArgs(ctx context.Context, key string, options *BFReserveOptions) *StatusCmd
BFScanDump(ctx context.Context, key string, iterator int64) *ScanDumpCmd
BFLoadChunk(ctx context.Context, key string, iterator int64, data interface{}) *StatusCmd
@@ -38,7 +38,7 @@ type ProbabilisticCmdable interface {
CFInsertNX(ctx context.Context, key string, options *CFInsertOptions, elements ...interface{}) *IntSliceCmd
CFMExists(ctx context.Context, key string, elements ...interface{}) *BoolSliceCmd
CFReserve(ctx context.Context, key string, capacity int64) *StatusCmd
- CFReserveArgs(ctx context.Context, key string, options *CFReserveOptions) *StatusCmd
+ CFReserveWithArgs(ctx context.Context, key string, options *CFReserveOptions) *StatusCmd
CFReserveExpansion(ctx context.Context, key string, capacity int64, expansion int64) *StatusCmd
CFReserveBucketSize(ctx context.Context, key string, capacity int64, bucketsize int64) *StatusCmd
CFReserveMaxIterations(ctx context.Context, key string, capacity int64, maxiterations int64) *StatusCmd
@@ -143,11 +143,11 @@ func (c cmdable) BFReserveNonScaling(ctx context.Context, key string, errorRate
return cmd
}
-// BFReserveArgs creates an empty Bloom filter with a single sub-filter
+// BFReserveWithArgs creates an empty Bloom filter with a single sub-filter
// for the initial specified capacity and with an upper bound error_rate.
// This function also allows for specifying additional options such as expansion rate and non-scaling behavior.
// For more information - https://redis.io/commands/bf.reserve/
-func (c cmdable) BFReserveArgs(ctx context.Context, key string, options *BFReserveOptions) *StatusCmd {
+func (c cmdable) BFReserveWithArgs(ctx context.Context, key string, options *BFReserveOptions) *StatusCmd {
args := []interface{}{"BF.RESERVE", key}
if options != nil {
if options.Error != 0 {
@@ -493,10 +493,10 @@ func (c cmdable) CFReserveMaxIterations(ctx context.Context, key string, capacit
return cmd
}
-// CFReserveArgs creates an empty Cuckoo filter with the specified options.
+// CFReserveWithArgs creates an empty Cuckoo filter with the specified options.
// This function allows for specifying additional options such as bucket size and maximum number of iterations.
// For more information - https://redis.io/commands/cf.reserve/
-func (c cmdable) CFReserveArgs(ctx context.Context, key string, options *CFReserveOptions) *StatusCmd {
+func (c cmdable) CFReserveWithArgs(ctx context.Context, key string, options *CFReserveOptions) *StatusCmd {
args := []interface{}{"CF.RESERVE", key, options.Capacity}
if options.BucketSize != 0 {
args = append(args, "BUCKETSIZE", options.BucketSize)
@@ -679,7 +679,7 @@ func (c cmdable) CFInfo(ctx context.Context, key string) *CFInfoCmd {
// For more information - https://redis.io/commands/cf.insert/
func (c cmdable) CFInsert(ctx context.Context, key string, options *CFInsertOptions, elements ...interface{}) *BoolSliceCmd {
args := []interface{}{"CF.INSERT", key}
- args = c.getCfInsertArgs(args, options, elements...)
+ args = c.getCfInsertWithArgs(args, options, elements...)
cmd := NewBoolSliceCmd(ctx, args...)
_ = c(ctx, cmd)
@@ -693,14 +693,14 @@ func (c cmdable) CFInsert(ctx context.Context, key string, options *CFInsertOpti
// For more information - https://redis.io/commands/cf.insertnx/
func (c cmdable) CFInsertNX(ctx context.Context, key string, options *CFInsertOptions, elements ...interface{}) *IntSliceCmd {
args := []interface{}{"CF.INSERTNX", key}
- args = c.getCfInsertArgs(args, options, elements...)
+ args = c.getCfInsertWithArgs(args, options, elements...)
cmd := NewIntSliceCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
-func (c cmdable) getCfInsertArgs(args []interface{}, options *CFInsertOptions, elements ...interface{}) []interface{} {
+func (c cmdable) getCfInsertWithArgs(args []interface{}, options *CFInsertOptions, elements ...interface{}) []interface{} {
if options != nil {
if options.Capacity != 0 {
args = append(args, "CAPACITY", options.Capacity)
diff --git a/probabilistic_test.go b/probabilistic_test.go
index 4b0dde646..b493abd46 100644
--- a/probabilistic_test.go
+++ b/probabilistic_test.go
@@ -227,14 +227,14 @@ var _ = Describe("Probabilistic commands", Label("probabilistic"), func() {
Expect(infBefore).To(BeEquivalentTo(infAfter))
})
- It("should BFReserveArgs", Label("bloom", "bfreserveargs"), func() {
+ It("should BFReserveWithArgs", Label("bloom", "bfreserveargs"), func() {
options := &redis.BFReserveOptions{
Capacity: 2000,
Error: 0.001,
Expansion: 3,
NonScaling: false,
}
- err := client.BFReserveArgs(ctx, "testbf", options).Err()
+ err := client.BFReserveWithArgs(ctx, "testbf", options).Err()
Expect(err).NotTo(HaveOccurred())
result, err := client.BFInfo(ctx, "testbf").Result()
@@ -352,7 +352,7 @@ var _ = Describe("Probabilistic commands", Label("probabilistic"), func() {
Expect(infBefore).To(BeEquivalentTo(infAfter))
})
- It("should CFInfo and CFReserveArgs", Label("cuckoo", "cfinfo", "cfreserveargs"), func() {
+ It("should CFInfo and CFReserveWithArgs", Label("cuckoo", "cfinfo", "cfreserveargs"), func() {
args := &redis.CFReserveOptions{
Capacity: 2048,
BucketSize: 3,
@@ -360,7 +360,7 @@ var _ = Describe("Probabilistic commands", Label("probabilistic"), func() {
Expansion: 2,
}
- err := client.CFReserveArgs(ctx, "testcf1", args).Err()
+ err := client.CFReserveWithArgs(ctx, "testcf1", args).Err()
Expect(err).NotTo(HaveOccurred())
result, err := client.CFInfo(ctx, "testcf1").Result()
diff --git a/redis.go b/redis.go
index c7fbd0de8..9430eb75c 100644
--- a/redis.go
+++ b/redis.go
@@ -299,7 +299,14 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
// difficult to rely on error strings to determine all results.
return err
}
-
+ if !c.opt.DisableIndentity {
+ libName := ""
+ libVer := Version()
+ libInfo := LibraryInfo{LibName: &libName}
+ conn.ClientSetInfo(ctx, libInfo)
+ libInfo = LibraryInfo{LibVer: &libVer}
+ conn.ClientSetInfo(ctx, libInfo)
+ }
_, err := conn.Pipelined(ctx, func(pipe Pipeliner) error {
if !auth && password != "" {
if username != "" {