Repository: redis/go-redis

Issue:
[BUG] FailoverClusterClient ignore DB option
FailoverClusterClient ignore DB from config

## Expected Behavior

FailoverClusterClient use DB from FailoverOptions

## Current Behavior

FailoverClusterClient use default DB: 0 option


Discussion:
@ndyakov: @khasanovbi thank you for reporting, as you can see from https://github.com/redis/go-redis/blob/d86395694d5427371c8f8cd087e319ead9f952a8/universal.go#L20 , the DB option is available only for single-node clients (and single node failover client). It is not available for Cluster Clients. @khasanovbi: Yes, but this is a valid Redis configuration.
I'm using Redis Sentinel. 
Previously I was using FailoverClient with DB: 1, but wanted to go not only to master but also to replicas with RouteByLatency.
To do this I switched to using FailoverClusterClient, I realize it is a pseudo cluster client but it should be able to go to DB:1.@ndyakov: @khasanovbi I understand your use case, but under the hood the `FailoverClusterClient` is using the `ClusterClient`. The `SELECT` command for selecting a database is not enabled in cluster mode. Although I am against adding the DB option for the cluster client, I think there is a good workaround you can utilize. You should be able to use https://github.com/redis/go-redis/blob/9762559c75eb695b30b7b0877a8dd9832a607f91/options.go#L46 and execute `cn.Select` yourself:
```
....
    OnConnect: func(ctx context.Context, cn *redis.Conn) error {
        _, err := cn.Select(ctx, 1)
        return err
    },
....
```
Feel free to close this if you are satisfied with the workaround.@khasanovbi: Ok, thank you for workaround, perhaps this should then be specified in the documentation?@khasanovbi: I tried such workaround, but it try to send select command to sentinel, instead of redis instance.
```
sentinel: GetMasterAddrByName master=\"test\" failed: ERR unknown command 'select', with args beginning with: '1' "
```
Maybe we could add SentinelDialer, OnSentinelConnect fields to FailoverOptions to distinguish such connections@ndyakov: Hm, correct, the sentinel client will inherit the same callback. I assume you can simply check if the `err` is the `unknown command` err and ignore it. Any other error should make sense for your application. @ndyakov: @khasanovbi I will close this issue for now, feel free to reopen if the solution doesn't work for you and you would like to discuss a different approach. @khasanovbi: Hi @ndyakov 
I don't really like this solution as we are making an extra call, and we also have to catch an error by message.
I would suggest moving the db select to the client constructor and prepare some small PR https://github.com/redis/go-redis/pull/3342, PTAL.

PS. I'm don't have permissions to reopen issue)

Solution patch:
diff --git a/hash_commands.go b/hash_commands.go
index 1f53f34..50d94ba 100644
--- a/hash_commands.go
+++ b/hash_commands.go
@@ -13,6 +13,7 @@ type HashCmdable interface {
 	HGetDel(ctx context.Context, key string, fields ...string) *StringSliceCmd
 	HGetEX(ctx context.Context, key string, fields ...string) *StringSliceCmd
 	HGetEXWithArgs(ctx context.Context, key string, options *HGetEXOptions, fields ...string) *StringSliceCmd
+	HIncrBy(ctx context.Context, key, field string, incr int64) *IntCmd
 	HIncrByFloat(ctx context.Context, key, field string, incr float64) *FloatCmd
 	HKeys(ctx context.Context, key string) *StringSliceCmd
 	HLen(ctx context.Context, key string) *IntCmd
diff --git a/search_commands.go b/search_commands.go
index 85e1256..4094262 100644
--- a/search_commands.go
+++ b/search_commands.go
@@ -320,8 +320,11 @@ type FTSearchOptions struct {
 	SortByWithCount bool
 	LimitOffset     int
 	Limit           int
-	Params          map[string]interface{}
-	DialectVersion  int
+	// CountOnly sets LIMIT 0 0 to get the count - number of documents in the result set without actually returning the result set.
+	// When using this option, the Limit and LimitOffset options are ignored.
+	CountOnly      bool
+	Params         map[string]interface{}
+	DialectVersion int
 }
 
 type FTSynDumpResult struct {
@@ -1954,8 +1957,12 @@ func (c cmdable) FTSearchWithArgs(ctx context.Context, index string, query strin
 				args = append(args, "WITHCOUNT")
 			}
 		}
-		if options.LimitOffset >= 0 && options.Limit > 0 {
-			args = append(args, "LIMIT", options.LimitOffset, options.Limit)
+		if options.CountOnly {
+			args = append(args, "LIMIT", 0, 0)
+		} else {
+			if options.LimitOffset >= 0 && options.Limit > 0 || options.LimitOffset > 0 && options.Limit == 0 {
+				args = append(args, "LIMIT", options.LimitOffset, options.Limit)
+			}
 		}
 		if options.Params != nil {
 			args = append(args, "PARAMS", len(options.Params)*2)
diff --git a/sentinel.go b/sentinel.go
index a4c9f53..a132af2 100644
--- a/sentinel.go
+++ b/sentinel.go
@@ -815,6 +815,22 @@ func NewFailoverClusterClient(failoverOpt *FailoverOptions) *ClusterClient {
 	}
 
 	opt := failoverOpt.clusterOptions()
+	if failoverOpt.DB != 0 {
+		onConnect := opt.OnConnect
+
+		opt.OnConnect = func(ctx context.Context, cn *Conn) error {
+			if err := cn.Select(ctx, failoverOpt.DB).Err(); err != nil {
+				return err
+			}
+
+			if onConnect != nil {
+				return onConnect(ctx, cn)
+			}
+
+			return nil
+		}
+	}
+
 	opt.ClusterSlots = func(ctx context.Context) ([]ClusterSlot, error) {
 		masterAddr, err := failover.MasterAddr(ctx)
 		if err != nil {



Test patch:
diff --git a/search_test.go b/search_test.go
index 4d8417d..3c4457a 100644
--- a/search_test.go
+++ b/search_test.go
@@ -1683,6 +1683,44 @@ var _ = Describe("RediSearch commands Resp 2", Label("search"), func() {
 		Expect(resUint8.Docs[0].ID).To(BeEquivalentTo("doc1"))
 	})
 
+	It("should test ft.search with CountOnly param", Label("search", "ftsearch"), func() {
+		val, err := client.FTCreate(ctx, "txtIndex", &redis.FTCreateOptions{},
+			&redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText},
+		).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(val).To(BeEquivalentTo("OK"))
+		WaitForIndexing(client, "txtIndex")
+
+		_, err = client.HSet(ctx, "doc1", "txt", "hello world").Result()
+		Expect(err).NotTo(HaveOccurred())
+		_, err = client.HSet(ctx, "doc2", "txt", "hello go").Result()
+		Expect(err).NotTo(HaveOccurred())
+		_, err = client.HSet(ctx, "doc3", "txt", "hello redis").Result()
+		Expect(err).NotTo(HaveOccurred())
+
+		optsCountOnly := &redis.FTSearchOptions{
+			CountOnly:      true,
+			LimitOffset:    0,
+			Limit:          2, // even though we limit to 2, with count-only no docs are returned
+			DialectVersion: 2,
+		}
+		resCountOnly, err := client.FTSearchWithArgs(ctx, "txtIndex", "hello", optsCountOnly).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(resCountOnly.Total).To(BeEquivalentTo(3))
+		Expect(len(resCountOnly.Docs)).To(BeEquivalentTo(0))
+
+		optsLimit := &redis.FTSearchOptions{
+			CountOnly:      false,
+			LimitOffset:    0,
+			Limit:          2, // we expect to get 2 documents even though total count is 3
+			DialectVersion: 2,
+		}
+		resLimit, err := client.FTSearchWithArgs(ctx, "txtIndex", "hello", optsLimit).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(resLimit.Total).To(BeEquivalentTo(3))
+		Expect(len(resLimit.Docs)).To(BeEquivalentTo(2))
+	})
+
 })
 
 func _assert_geosearch_result(result *redis.FTSearchResult, expectedDocIDs []string) {
diff --git a/sentinel_test.go b/sentinel_test.go
index b34706f..07c7628 100644
--- a/sentinel_test.go
+++ b/sentinel_test.go
@@ -200,6 +200,7 @@ var _ = Describe("NewFailoverClusterClient", func() {
 			SentinelAddrs: sentinelAddrs,
 
 			RouteRandomly: true,
+			DB:            1,
 		})
 		Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
 
@@ -289,6 +290,20 @@ var _ = Describe("NewFailoverClusterClient", func() {
 		})
 	})
 
+	It("should sentinel cluster client db", func() {
+		err := client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
+			return c.Ping(ctx).Err()
+		})
+		Expect(err).NotTo(HaveOccurred())
+
+		_ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
+			clientInfo, err := c.ClientInfo(ctx).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(clientInfo.DB).To(Equal(1))
+			return nil
+		})
+	})
+
 	It("should sentinel cluster PROTO 3", func() {
 		_ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
 			val, err := client.Do(ctx, "HELLO").Result()