Repository: gomodule/redigo (go)

Issue:
github.com/gomodule/redigo/redis in multiple modules
```
github.com/gomodule/redigo/redis: ambiguous import: found package github.com/gomodule/redigo/redis in multiple modules:
        github.com/gomodule/redigo v2.0.0+incompatible (/usr/local/go/workplace/pkg/mod/github.com/gomodule/redigo@v2.0.0+incompatible/redis)
        github.com/gomodule/redigo/redis v0.0.0-20200429221454-e14091dffc1b (/usr/local/go/workplace/pkg/mod/github.com/gomodule/redigo/redis@v0.0.0-20200429221454-e14091dffc1b)
```


Pull request:
Update README.markdown
Prevent dependency problem

Fixes #485

Patch:
diff --git a/README.markdown b/README.markdown
index 2a637ac..054db54 100644
--- a/README.markdown
+++ b/README.markdown
@@ -28,7 +28,7 @@ Installation
 
 Install Redigo using the "go get" command:
 
-    go get github.com/gomodule/redigo/redis
+    go get github.com/gomodule/redigo
 
 The Go distribution is Redigo's only dependency.
 
diff --git a/redis/conn.go b/redis/conn.go
index a66e757..33b43be 100644
--- a/redis/conn.go
+++ b/redis/conn.go
@@ -80,6 +80,7 @@ type dialOptions struct {
 	dialer       *net.Dialer
 	dialContext  func(ctx context.Context, network, addr string) (net.Conn, error)
 	db           int
+	username     string
 	password     string
 	clientName   string
 	useTLS       bool
@@ -154,6 +155,14 @@ func DialPassword(password string) DialOption {
 	}}
 }
 
+// DialUsername specifies the username to use when connecting to
+// the Redis server when Redis ACLs are used.
+func DialUsername(username string) DialOption {
+	return DialOption{func(do *dialOptions) {
+		do.username = username
+	}}
+}
+
 // DialClientName specifies a client name to be used
 // by the Redis server connection.
 func DialClientName(name string) DialOption {
@@ -245,7 +254,12 @@ func DialContext(ctx context.Context, network, address string, options ...DialOp
 	}
 
 	if do.password != "" {
-		if _, err := c.Do("AUTH", do.password); err != nil {
+		authArgs := make([]interface{}, 0, 2)
+		if do.username != "" {
+			authArgs = append(authArgs, do.username)
+		}
+		authArgs = append(authArgs, do.password)
+		if _, err := c.Do("AUTH", authArgs...); err != nil {
 			netConn.Close()
 			return nil, err
 		}
@@ -303,7 +317,7 @@ func DialURL(rawurl string, options ...DialOption) (Conn, error) {
 	if u.User != nil {
 		password, isSet := u.User.Password()
 		if isSet {
-			options = append(options, DialPassword(password))
+			options = append(options, DialUsername(u.User.Username()), DialPassword(password))
 		}
 	}
 
diff --git a/redis/pool.go b/redis/pool.go
index 254c034..713eab3 100644
--- a/redis/pool.go
+++ b/redis/pool.go
@@ -199,27 +199,10 @@ func (p *Pool) Get() Conn {
 // If the function completes without error, then the application must close the
 // returned connection.
 func (p *Pool) GetContext(ctx context.Context) (Conn, error) {
-	// Handle limit for p.Wait == true.
-	var waited time.Duration
-	if p.Wait && p.MaxActive > 0 {
-		p.lazyInit()
-
-		// wait indicates if we believe it will block so its not 100% accurate
-		// however for stats it should be good enough.
-		wait := len(p.ch) == 0
-		var start time.Time
-		if wait {
-			start = time.Now()
-		}
-		select {
-		case <-p.ch:
-		case <-ctx.Done():
-			err := ctx.Err()
-			return errorConn{err}, err
-		}
-		if wait {
-			waited = time.Since(start)
-		}
+	// Wait until there is a vacant connection in the pool.
+	waited, err := p.waitVacantConn(ctx)
+	if err != nil {
+		return errorConn{err}, err
 	}
 
 	p.mu.Lock()
@@ -376,6 +359,51 @@ func (p *Pool) lazyInit() {
 	p.mu.Unlock()
 }
 
+// waitVacantConn waits for a vacant connection in pool if waiting
+// is enabled and pool size is limited, otherwise returns instantly.
+// If ctx expires before that, an error is returned.
+//
+// If there were no vacant connection in the pool right away it returns the time spent waiting
+// for that connection to appear in the pool.
+func (p *Pool) waitVacantConn(ctx context.Context) (waited time.Duration, err error) {
+	if !p.Wait || p.MaxActive <= 0 {
+		// No wait or no connection limit.
+		return 0, nil
+	}
+
+	p.lazyInit()
+
+	// wait indicates if we believe it will block so its not 100% accurate
+	// however for stats it should be good enough.
+	wait := len(p.ch) == 0
+	var start time.Time
+	if wait {
+		start = time.Now()
+	}
+
+	if ctx == nil {
+		<-p.ch
+	} else {
+		select {
+		case <-p.ch:
+			// Additionally check that context hasn't expired while we were waiting,
+			// because `select` picks a random `case` if several of them are "ready".
+			select {
+			case <-ctx.Done():
+				return 0, ctx.Err()
+			default:
+			}
+		case <-ctx.Done():
+			return 0, ctx.Err()
+		}
+	}
+
+	if wait {
+		return time.Since(start), nil
+	}
+	return 0, nil
+}
+
 func (p *Pool) dial(ctx context.Context) (Conn, error) {
 	if p.DialContext != nil {
 		return p.DialContext(ctx)
diff --git a/redis/scan.go b/redis/scan.go
index 289cfd9..93d0c65 100644
--- a/redis/scan.go
+++ b/redis/scan.go
@@ -23,6 +23,10 @@ import (
 	"sync"
 )
 
+var (
+	scannerType = reflect.TypeOf((*Scanner)(nil)).Elem()
+)
+
 func ensureLen(d reflect.Value, n int) {
 	if n > d.Cap() {
 		d.Set(reflect.MakeSlice(d.Type(), n, n))
@@ -358,10 +362,14 @@ func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *st
 		case f.PkgPath != "" && !f.Anonymous:
 			// Ignore unexported fields.
 		case f.Anonymous:
-			// TODO: Handle pointers. Requires change to decoder and
-			// protection against infinite recursion.
-			if f.Type.Kind() == reflect.Struct {
+			switch f.Type.Kind() {
+			case reflect.Struct:
 				compileStructSpec(f.Type, depth, append(index, i), ss)
+			case reflect.Ptr:
+				// TODO(steve): Protect against infinite recursion.
+				if f.Type.Elem().Kind() == reflect.Struct {
+					compileStructSpec(f.Type.Elem(), depth, append(index, i), ss)
+				}
 			}
 		default:
 			fs := &fieldSpec{name: f.Name}
@@ -496,9 +504,13 @@ var (
 	errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct")
 )
 
-// ScanSlice scans src to the slice pointed to by dest. The elements the dest
-// slice must be integer, float, boolean, string, struct or pointer to struct
-// values.
+// ScanSlice scans src to the slice pointed to by dest.
+//
+// If the target is a slice of types which implement Scanner then the custom
+// RedisScan method is used otherwise the following rules apply:
+//
+// The elements in the dest slice must be integer, float, boolean, string, struct
+// or pointer to struct values.
 //
 // Struct fields must be integer, float, boolean or string values. All struct
 // fields are used unless a subset is specified using fieldNames.
@@ -514,12 +526,13 @@ func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error
 
 	isPtr := false
 	t := d.Type().Elem()
+	st := t
 	if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
 		isPtr = true
 		t = t.Elem()
 	}
 
-	if t.Kind() != reflect.Struct {
+	if t.Kind() != reflect.Struct || st.Implements(scannerType) {
 		ensureLen(d, len(src))
 		for i, s := range src {
 			if s == nil {


Test patch:
diff --git a/redis/conn_test.go b/redis/conn_test.go
index 5864aca..dbc66e7 100644
--- a/redis/conn_test.go
+++ b/redis/conn_test.go
@@ -636,7 +636,9 @@ var dialURLTests = []struct {
 	r           string
 	w           string
 }{
-	{"password", "redis://x:abc123@localhost", "+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"},
+	{"password", "redis://:abc123@localhost", "+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"},
+	{"username and password", "redis://user:password@localhost", "+OK\r\n", "*3\r\n$4\r\nAUTH\r\n$4\r\nuser\r\n$8\r\npassword\r\n"},
+	{"username", "redis://x:@localhost", "+OK\r\n", ""},
 	{"database 3", "redis://localhost/3", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"},
 	{"database 99", "redis://localhost/99", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$2\r\n99\r\n"},
 	{"no database", "redis://localhost/", "+OK\r\n", ""},
@@ -711,6 +713,36 @@ func TestDialTLSSKipVerify(t *testing.T) {
 	checkPingPong(t, &buf, c)
 }
 
+func TestDialUseACL(t *testing.T) {
+	var buf bytes.Buffer
+	_, err := redis.Dial("tcp", "localhost:6379",
+		redis.DialUsername("user"),
+		redis.DialPassword("password"),
+		dialTestConn(pingResponse, &buf))
+	if err != nil {
+		t.Fatal("dial error:", err)
+	}
+	if err != nil {
+		t.Fatal("dial error:", err)
+	}
+	expected := "*3\r\n$4\r\nAUTH\r\n$4\r\nuser\r\n$8\r\npassword\r\n"
+	if w := buf.String(); w != expected {
+		t.Errorf("got %q, want %q", w, expected)
+	}
+}
+
+// Connect to an Redis instance using the Redis ACL system
+func ExampleDial_acl() {
+	c, err := redis.Dial("tcp", "localhost:6379",
+		redis.DialUsername("username"),
+		redis.DialPassword("password"),
+	)
+	if err != nil {
+		// handle error
+	}
+	defer c.Close()
+}
+
 func TestDialClientName(t *testing.T) {
 	var buf bytes.Buffer
 	_, err := redis.Dial("tcp", ":6379",
diff --git a/redis/pool_test.go b/redis/pool_test.go
index 3e46bc4..a8efa51 100644
--- a/redis/pool_test.go
+++ b/redis/pool_test.go
@@ -899,20 +899,38 @@ func TestWaitPoolGetAfterClose(t *testing.T) {
 }
 
 func TestWaitPoolGetCanceledContext(t *testing.T) {
-	d := poolDialer{t: t}
-	p := &redis.Pool{
-		MaxIdle:   1,
-		MaxActive: 1,
-		Dial:      d.dial,
-		Wait:      true,
-	}
-	defer p.Close()
-	ctx, f := context.WithCancel(context.Background())
-	f()
-	c := p.Get()
-	defer c.Close()
-	_, err := p.GetContext(ctx)
-	if err != context.Canceled {
-		t.Fatalf("got error %v, want %v", err, context.Canceled)
-	}
+	t.Run("without vacant connection in the pool", func(t *testing.T) {
+		d := poolDialer{t: t}
+		p := &redis.Pool{
+			MaxIdle:   1,
+			MaxActive: 1,
+			Dial:      d.dial,
+			Wait:      true,
+		}
+		defer p.Close()
+		ctx, cancel := context.WithCancel(context.Background())
+		cancel()
+		c := p.Get()
+		defer c.Close()
+		_, err := p.GetContext(ctx)
+		if err != context.Canceled {
+			t.Fatalf("got error %v, want %v", err, context.Canceled)
+		}
+	})
+	t.Run("with vacant connection in the pool", func(t *testing.T) {
+		d := poolDialer{t: t}
+		p := &redis.Pool{
+			MaxIdle:   1,
+			MaxActive: 1,
+			Dial:      d.dial,
+			Wait:      true,
+		}
+		defer p.Close()
+		ctx, cancel := context.WithCancel(context.Background())
+		cancel()
+		_, err := p.GetContext(ctx)
+		if err != context.Canceled {
+			t.Fatalf("got error %v, want %v", err, context.Canceled)
+		}
+	})
 }
diff --git a/redis/scan_test.go b/redis/scan_test.go
index 67b1aac..445e071 100644
--- a/redis/scan_test.go
+++ b/redis/scan_test.go
@@ -298,67 +298,90 @@ func TestBadScanStructArgs(t *testing.T) {
 	test(&v2)
 }
 
+type sliceScanner struct {
+	Field string
+}
+
+func (ss *sliceScanner) RedisScan(s interface{}) error {
+	v, ok := s.([]interface{})
+	if !ok {
+		return fmt.Errorf("invalid type %T", s)
+	}
+	return redis.ScanStruct(v, ss)
+}
+
 var scanSliceTests = []struct {
+	name       string
 	src        []interface{}
 	fieldNames []string
 	ok         bool
 	dest       interface{}
 }{
 	{
+		"scanner",
+		[]interface{}{[]interface{}{[]byte("Field"), []byte("1")}},
+		nil,
+		true,
+		[]*sliceScanner{{"1"}},
+	},
+	{
+		"int",
 		[]interface{}{[]byte("1"), nil, []byte("-1")},
 		nil,
 		true,
 		[]int{1, 0, -1},
 	},
 	{
+		"uint",
 		[]interface{}{[]byte("1"), nil, []byte("2")},
 		nil,
 		true,
 		[]uint{1, 0, 2},
 	},
 	{
+		"uint-error",
 		[]interface{}{[]byte("-1")},
 		nil,
 		false,
 		[]uint{1},
 	},
 	{
+		"[]byte",
 		[]interface{}{[]byte("hello"), nil, []byte("world")},
 		nil,
 		true,
 		[][]byte{[]byte("hello"), nil, []byte("world")},
 	},
 	{
+		"string",
 		[]interface{}{[]byte("hello"), nil, []byte("world")},
 		nil,
 		true,
 		[]string{"hello", "", "world"},
 	},
 	{
+		"struct",
 		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
 		nil,
 		true,
 		[]struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}},
 	},
 	{
+		"struct-error",
 		[]interface{}{[]byte("a1"), []byte("b1")},
 		nil,
 		false,
 		[]struct{ A, B, C string }{{"a1", "b1", ""}},
 	},
 	{
-		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
-		nil,
-		true,
-		[]*struct{ A, B string }{{A: "a1", B: "b1"}, {A: "a2", B: "b2"}},
-	},
-	{
+		"struct-field-names",
 		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
 		[]string{"A", "B"},
 		true,
 		[]struct{ A, C, B string }{{"a1", "", "b1"}, {"a2", "", "b2"}},
 	},
 	{
+		"struct-no-fields",
 		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
 		nil,
 		false,
@@ -368,18 +391,18 @@ var scanSliceTests = []struct {
 
 func TestScanSlice(t *testing.T) {
 	for _, tt := range scanSliceTests {
+		t.Run(tt.name, func(t *testing.T) {
+			typ := reflect.ValueOf(tt.dest).Type()
+			dest := reflect.New(typ)
 
-		typ := reflect.ValueOf(tt.dest).Type()
-		dest := reflect.New(typ)
-
-		err := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...)
-		if tt.ok != (err == nil) {
-			t.Errorf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err)
-			continue
-		}
-		if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) {
-			t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest)
-		}
+			err := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...)
+			if tt.ok != (err == nil) {
+				t.Fatalf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err)
+			}
+			if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) {
+				t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest)
+			}
+		})
 	}
 }
 
@@ -421,6 +444,14 @@ func ExampleScanSlice() {
 
 var now = time.Now()
 
+type Ed struct {
+	EdI int `redis:"edi"`
+}
+
+type Edp struct {
+	EdpI int `redis:"edpi"`
+}
+
 var argsTests = []struct {
 	title    string
 	actual   redis.Args
@@ -470,6 +501,16 @@ var argsTests = []struct {
 		}),
 		redis.Args{},
 	},
+	{"struct-anonymous",
+		redis.Args{}.AddFlat(struct {
+			Ed
+			*Edp
+		}{
+			Ed{EdI: 2},
+			&Edp{EdpI: 3},
+		}),
+		redis.Args{"edi", 2, "edpi", 3},
+	},
 }
 
 func TestArgs(t *testing.T) {