Repository: go-redis/redis
Pull request #1273 (2020-03-13 16:58:14)

Store time in RFC3339Nano to keep nanoseconds
Hi,

Time package has nanosecond precision.
Actually go-redis removes this precision when time is stored or fetched with RFC3339.

```
t := time.Now() // time with nanoseconds precision
_ := redisClient.Set("my-time", t, 0).Err()

ft, _ := redisClient.Get("my-time").Time() 

// ft != t
```

To deal with these nanoseconds, I suggest RFC3339Nano.


Patch:
diff --git a/.travis.yml b/.travis.yml
index a19ea1c..0c1eef9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,5 @@
 dist: xenial
+sudo: false
 language: go
 
 services:
diff --git a/README.md b/README.md
index 0fbb506..7331a56 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ set, err := client.SetNX("key", "value", 10*time.Second).Result()
 vals, err := client.Sort("list", &redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result()
 
 // ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2
-vals, err := client.ZRangeByScoreWithScores("zset", &redis.ZRangeBy{
+vals, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{
 	Min: "-inf",
 	Max: "+inf",
 	Offset: 0,
@@ -109,10 +109,7 @@ vals, err := client.ZRangeByScoreWithScores("zset", &redis.ZRangeBy{
 }).Result()
 
 // ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM
-vals, err := client.ZInterStore("out", &redis.ZStore{
-	Keys: []string{"zset1", "zset2"},
-	Weights: []int64{2, 3}
-}).Result()
+vals, err := client.ZInterStore("out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2").Result()
 
 // EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello"
 vals, err := client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result()
diff --git a/cluster.go b/cluster.go
index f02fcdf..2cf6e01 100644
--- a/cluster.go
+++ b/cluster.go
@@ -57,7 +57,6 @@ type ClusterOptions struct {
 
 	OnConnect func(*Conn) error
 
-	Username string
 	Password string
 
 	MaxRetries      int
@@ -131,7 +130,6 @@ func (opt *ClusterOptions) clientOptions() *Options {
 		MaxRetries:      opt.MaxRetries,
 		MinRetryBackoff: opt.MinRetryBackoff,
 		MaxRetryBackoff: opt.MaxRetryBackoff,
-		Username:        opt.Username,
 		Password:        opt.Password,
 		readOnly:        opt.ReadOnly,
 
diff --git a/command.go b/command.go
index 266e389..fa1b03e 100644
--- a/command.go
+++ b/command.go
@@ -671,7 +671,7 @@ func (cmd *StringCmd) Time() (time.Time, error) {
 	if cmd.err != nil {
 		return time.Time{}, cmd.err
 	}
-	return time.Parse(time.RFC3339, cmd.Val())
+	return time.Parse(time.RFC3339Nano, cmd.Val())
 }
 
 func (cmd *StringCmd) Scan(val interface{}) error {
diff --git a/commands.go b/commands.go
index da5ceda..d4447c4 100644
--- a/commands.go
+++ b/commands.go
@@ -302,7 +302,6 @@ type Cmdable interface {
 type StatefulCmdable interface {
 	Cmdable
 	Auth(password string) *StatusCmd
-	AuthACL(username, password string) *StatusCmd
 	Select(index int) *StatusCmd
 	SwapDB(index1, index2 int) *StatusCmd
 	ClientSetName(name string) *BoolCmd
@@ -325,15 +324,6 @@ func (c statefulCmdable) Auth(password string) *StatusCmd {
 	return cmd
 }
 
-// Perform an AUTH command, using the given user and pass.
-// Should be used to authenticate the current connection with one of the connections defined in the ACL list
-// when connecting to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
-func (c statefulCmdable) AuthACL(username, password string) *StatusCmd {
-	cmd := NewStatusCmd("auth", username, password)
-	_ = c(cmd)
-	return cmd
-}
-
 func (c cmdable) Echo(message interface{}) *StringCmd {
 	cmd := NewStringCmd("echo", message)
 	_ = c(cmd)
diff --git a/internal/pool/pool.go b/internal/pool/pool.go
index a8d8276..bbf5b30 100644
--- a/internal/pool/pool.go
+++ b/internal/pool/pool.go
@@ -94,9 +94,7 @@ func NewConnPool(opt *Options) *ConnPool {
 		closedCh:  make(chan struct{}),
 	}
 
-	p.connsMu.Lock()
 	p.checkMinIdleConns()
-	p.connsMu.Unlock()
 
 	if opt.IdleTimeout > 0 && opt.IdleCheckFrequency > 0 {
 		go p.reaper(opt.IdleCheckFrequency)
diff --git a/internal/proto/writer.go b/internal/proto/writer.go
index cd83d65..d552f1e 100644
--- a/internal/proto/writer.go
+++ b/internal/proto/writer.go
@@ -93,7 +93,7 @@ func (w *Writer) writeArg(v interface{}) error {
 		}
 		return w.int(0)
 	case time.Time:
-		return w.string(v.Format(time.RFC3339))
+		return w.string(v.Format(time.RFC3339Nano))
 	case encoding.BinaryMarshaler:
 		b, err := v.MarshalBinary()
 		if err != nil {
diff --git a/options.go b/options.go
index 47dcc29..621d3a3 100644
--- a/options.go
+++ b/options.go
@@ -40,13 +40,8 @@ type Options struct {
 	// Hook that is called when new connection is established.
 	OnConnect func(*Conn) error
 
-	// Use the specified Username to authenticate the current connection with one of the connections defined in the ACL
-	// list when connecting to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
-	Username string
-
 	// Optional password. Must match the password specified in the
-	// requirepass server configuration option (if connecting to a Redis 5.0 instance, or lower),
-	// or the User Password when connecting to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
+	// requirepass server configuration option.
 	Password string
 	// Database to be selected after connecting to the server.
 	DB int
@@ -192,7 +187,6 @@ func ParseURL(redisURL string) (*Options, error) {
 	}
 
 	if u.User != nil {
-		o.Username = u.User.Username()
 		if p, ok := u.User.Password(); ok {
 			o.Password = p
 		}
diff --git a/redis.go b/redis.go
index 3d9dfed..9303257 100644
--- a/redis.go
+++ b/redis.go
@@ -241,11 +241,7 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
 
 	_, err := conn.Pipelined(func(pipe Pipeliner) error {
 		if c.opt.Password != "" {
-			if c.opt.Username != "" {
-				pipe.AuthACL(c.opt.Username, c.opt.Password)
-			} else {
-				pipe.Auth(c.opt.Password)
-			}
+			pipe.Auth(c.opt.Password)
 		}
 
 		if c.opt.DB > 0 {
diff --git a/result.go b/result.go
index 5bec26c..4b7de63 100644
--- a/result.go
+++ b/result.go
@@ -98,14 +98,6 @@ func NewStringIntMapCmdResult(val map[string]int64, err error) *StringIntMapCmd
 	return &cmd
 }
 
-// NewTimeCmdResult returns a TimeCmd initialised with val and err for testing
-func NewTimeCmdResult(val time.Time, err error) *TimeCmd {
-	var cmd TimeCmd
-	cmd.val = val
-	cmd.SetErr(err)
-	return &cmd
-}
-
 // NewZSliceCmdResult returns a ZSliceCmd initialised with val and err for testing
 func NewZSliceCmdResult(val []Z, err error) *ZSliceCmd {
 	var cmd ZSliceCmd
diff --git a/ring.go b/ring.go
index 44fc623..e1b4991 100644
--- a/ring.go
+++ b/ring.go
@@ -56,9 +56,6 @@ type RingOptions struct {
 	//  See https://arxiv.org/abs/1406.2294 for reference
 	HashReplicas int
 
-	// NewClient creates a shard client with provided name and options.
-	NewClient func(name string, opt *Options) *Client
-
 	// Optional hook that is called when a new shard is created.
 	OnNewShard func(*Client)
 
@@ -393,12 +390,7 @@ func NewRing(opt *RingOptions) *Ring {
 func newRingShard(opt *RingOptions, name, addr string) *Client {
 	clopt := opt.clientOptions(name)
 	clopt.Addr = addr
-	var shard *Client
-	if opt.NewClient != nil {
-		shard = opt.NewClient(name, clopt)
-	} else {
-		shard = NewClient(clopt)
-	}
+	shard := NewClient(clopt)
 	if opt.OnNewShard != nil {
 		opt.OnNewShard(shard)
 	}
diff --git a/sentinel.go b/sentinel.go
index 8aa40ef..6487ef6 100644
--- a/sentinel.go
+++ b/sentinel.go
@@ -22,7 +22,6 @@ type FailoverOptions struct {
 	MasterName string
 	// A seed list of host:port addresses of sentinel nodes.
 	SentinelAddrs    []string
-	SentinelUsername string
 	SentinelPassword string
 
 	// Following options are copied from Options struct.
@@ -30,7 +29,6 @@ type FailoverOptions struct {
 	Dialer    func(ctx context.Context, network, addr string) (net.Conn, error)
 	OnConnect func(*Conn) error
 
-	Username string
 	Password string
 	DB       int
 
@@ -59,7 +57,6 @@ func (opt *FailoverOptions) options() *Options {
 		OnConnect: opt.OnConnect,
 
 		DB:       opt.DB,
-		Username: opt.Username,
 		Password: opt.Password,
 
 		MaxRetries:      opt.MaxRetries,
@@ -91,7 +88,6 @@ func NewFailoverClient(failoverOpt *FailoverOptions) *Client {
 	failover := &sentinelFailover{
 		masterName:    failoverOpt.MasterName,
 		sentinelAddrs: failoverOpt.SentinelAddrs,
-		username:      failoverOpt.SentinelUsername,
 		password:      failoverOpt.SentinelPassword,
 
 		opt: opt,
@@ -285,7 +281,6 @@ type sentinelFailover struct {
 	sentinelAddrs []string
 
 	opt      *Options
-	username string
 	password string
 
 	pool     *pool.ConnPool
@@ -377,7 +372,6 @@ func (c *sentinelFailover) masterAddr() (string, error) {
 			Addr:   sentinelAddr,
 			Dialer: c.opt.Dialer,
 
-			Username: c.username,
 			Password: c.password,
 
 			MaxRetries: c.opt.MaxRetries,
diff --git a/universal.go b/universal.go
index 005ca68..21c4d07 100644
--- a/universal.go
+++ b/universal.go
@@ -22,7 +22,6 @@ type UniversalOptions struct {
 
 	Dialer             func(ctx context.Context, network, addr string) (net.Conn, error)
 	OnConnect          func(*Conn) error
-	Username           string
 	Password           string
 	MaxRetries         int
 	MinRetryBackoff    time.Duration
@@ -61,7 +60,6 @@ func (o *UniversalOptions) Cluster() *ClusterOptions {
 		Dialer:    o.Dialer,
 		OnConnect: o.OnConnect,
 
-		Username: o.Username,
 		Password: o.Password,
 
 		MaxRedirects:   o.MaxRedirects,
@@ -101,7 +99,6 @@ func (o *UniversalOptions) Failover() *FailoverOptions {
 		OnConnect: o.OnConnect,
 
 		DB:       o.DB,
-		Username: o.Username,
 		Password: o.Password,
 
 		MaxRetries:      o.MaxRetries,
@@ -136,7 +133,6 @@ func (o *UniversalOptions) Simple() *Options {
 		OnConnect: o.OnConnect,
 
 		DB:       o.DB,
-		Username: o.Username,
 		Password: o.Password,
 
 		MaxRetries:      o.MaxRetries,


Test patch:
diff --git a/command_test.go b/command_test.go
index e9fc958..c7ecbca 100644
--- a/command_test.go
+++ b/command_test.go
@@ -72,14 +72,14 @@ var _ = Describe("Cmd", func() {
 	})
 
 	It("supports time.Time", func() {
-		tm := time.Date(2019, 01, 01, 0, 0, 0, 0, time.UTC)
+		tm := time.Date(2019, 01, 01, 9, 45, 10, 222125, time.UTC)
 
 		err := client.Set("time_key", tm, 0).Err()
 		Expect(err).NotTo(HaveOccurred())
 
 		s, err := client.Get("time_key").Result()
 		Expect(err).NotTo(HaveOccurred())
-		Expect(s).To(Equal("2019-01-01T00:00:00Z"))
+		Expect(s).To(Equal("2019-01-01T09:45:10.000222125Z"))
 
 		tm2, err := client.Get("time_key").Time()
 		Expect(err).NotTo(HaveOccurred())
diff --git a/internal/proto/write_buffer_test.go b/internal/proto/write_buffer_test.go
index 4a9339e..2953446 100644
--- a/internal/proto/write_buffer_test.go
+++ b/internal/proto/write_buffer_test.go
@@ -55,13 +55,14 @@ var _ = Describe("WriteBuffer", func() {
 	})
 
 	It("should append time", func() {
-		err := wr.WriteArgs([]interface{}{time.Unix(1414141414, 0).UTC()})
+		tm := time.Date(2019, 01, 01, 9, 45, 10, 222125, time.UTC)
+		err := wr.WriteArgs([]interface{}{tm})
 		Expect(err).NotTo(HaveOccurred())
 
 		err = wr.Flush()
 		Expect(err).NotTo(HaveOccurred())
 
-		Expect(buf.Len()).To(Equal(31))
+		Expect(buf.Len()).To(Equal(41))
 	})
 
 	It("should append marshalable args", func() {
diff --git a/options_test.go b/options_test.go
index f3468ff..9b806f4 100644
--- a/options_test.go
+++ b/options_test.go
@@ -15,86 +15,56 @@ func TestParseURL(t *testing.T) {
 		db   int
 		tls  bool
 		err  error
-		user string
-		pass string
 	}{
 		{
 			"redis://localhost:123/1",
 			"localhost:123",
 			1, false, nil,
-			"", "",
 		},
 		{
 			"redis://localhost:123",
 			"localhost:123",
 			0, false, nil,
-			"", "",
 		},
 		{
 			"redis://localhost/1",
 			"localhost:6379",
 			1, false, nil,
-			"", "",
 		},
 		{
 			"redis://12345",
 			"12345:6379",
 			0, false, nil,
-			"", "",
 		},
 		{
 			"rediss://localhost:123",
 			"localhost:123",
 			0, true, nil,
-			"", "",
-		},
-		{
-			"redis://:bar@localhost:123",
-			"localhost:123",
-			0, false, nil,
-			"", "bar",
-		},
-		{
-			"redis://foo@localhost:123",
-			"localhost:123",
-			0, false, nil,
-			"foo", "",
-		},
-		{
-			"redis://foo:bar@localhost:123",
-			"localhost:123",
-			0, false, nil,
-			"foo", "bar",
 		},
 		{
 			"redis://localhost/?abc=123",
 			"",
 			0, false, errors.New("no options supported"),
-			"", "",
 		},
 		{
 			"http://google.com",
 			"",
 			0, false, errors.New("invalid redis URL scheme: http"),
-			"", "",
 		},
 		{
 			"redis://localhost/1/2/3/4",
 			"",
 			0, false, errors.New("invalid redis URL path: /1/2/3/4"),
-			"", "",
 		},
 		{
 			"12345",
 			"",
 			0, false, errors.New("invalid redis URL scheme: "),
-			"", "",
 		},
 		{
 			"redis://localhost/iamadatabase",
 			"",
 			0, false, errors.New(`invalid redis database number: "iamadatabase"`),
-			"", "",
 		},
 	}
 
@@ -120,12 +90,6 @@ func TestParseURL(t *testing.T) {
 			if c.tls && o.TLSConfig == nil {
 				t.Errorf("got nil TLSConfig, expected a TLSConfig")
 			}
-			if o.Username != c.user {
-				t.Errorf("got %q, expected %q", o.Username, c.user)
-			}
-			if o.Password != c.pass {
-				t.Errorf("got %q, expected %q", o.Password, c.pass)
-			}
 		})
 	}
 }
diff --git a/ring_test.go b/ring_test.go
index e2ea991..eef8dc2 100644
--- a/ring_test.go
+++ b/ring_test.go
@@ -196,20 +196,6 @@ var _ = Describe("Redis Ring", func() {
 		})
 	})
 
-	Describe("new client callback", func() {
-		It("can be initialized with a new client callback", func() {
-			opts := redisRingOptions()
-			opts.NewClient = func(name string, opt *redis.Options) *redis.Client {
-				opt.Password = "password1"
-				return redis.NewClient(opt)
-			}
-			ring = redis.NewRing(opts)
-
-			err := ring.Ping().Err()
-			Expect(err).To(MatchError("ERR Client sent AUTH, but no password is set"))
-		})
-	})
-
 	It("supports Process hook", func() {
 		err := ring.Ping().Err()
 		Expect(err).NotTo(HaveOccurred())