From d42dd1007c1ed37cc32015129ad4fa77a8dbad74 Mon Sep 17 00:00:00 2001 From: monkey92t Date: Sat, 7 Jan 2023 16:30:56 +0800 Subject: [PATCH] docs: add a description of the hook Signed-off-by: monkey92t --- cluster.go | 53 ++++++++++++++------------- command.go | 17 ++++----- internal/once.go | 7 ++-- internal/proto/scan.go | 1 + options.go | 52 +++++++++++++++------------ redis.go | 81 ++++++++++++++++++++++++++++++------------ 6 files changed, 132 insertions(+), 79 deletions(-) diff --git a/cluster.go b/cluster.go index d36b5c36..872baad4 100644 --- a/cluster.go +++ b/cluster.go @@ -136,34 +136,39 @@ func (opt *ClusterOptions) init() { // ParseClusterURL parses a URL into ClusterOptions that can be used to connect to Redis. // The URL must be in the form: -// redis://:@: -// or -// rediss://:@: +// +// redis://:@: +// or +// rediss://:@: +// // To add additional addresses, specify the query parameter, "addr" one or more times. e.g: -// redis://:@:?addr=:&addr=: -// or -// rediss://:@:?addr=:&addr=: +// +// redis://:@:?addr=:&addr=: +// or +// rediss://:@:?addr=:&addr=: // // Most Option fields can be set using query parameters, with the following restrictions: -// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries -// - only scalar type fields are supported (bool, int, time.Duration) -// - for time.Duration fields, values must be a valid input for time.ParseDuration(); -// additionally a plain integer as value (i.e. without unit) is intepreted as seconds -// - to disable a duration field, use value less than or equal to 0; to use the default -// value, leave the value blank or remove the parameter -// - only the last value is interpreted if a parameter is given multiple times -// - fields "network", "addr", "username" and "password" can only be set using other -// URL attributes (scheme, host, userinfo, resp.), query paremeters using these -// names will be treated as unknown parameters -// - unknown parameter names will result in an error +// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries +// - only scalar type fields are supported (bool, int, time.Duration) +// - for time.Duration fields, values must be a valid input for time.ParseDuration(); +// additionally a plain integer as value (i.e. without unit) is intepreted as seconds +// - to disable a duration field, use value less than or equal to 0; to use the default +// value, leave the value blank or remove the parameter +// - only the last value is interpreted if a parameter is given multiple times +// - fields "network", "addr", "username" and "password" can only be set using other +// URL attributes (scheme, host, userinfo, resp.), query paremeters using these +// names will be treated as unknown parameters +// - unknown parameter names will result in an error +// // Example: -// redis://user:password@localhost:6789?dial_timeout=3&read_timeout=6s&addr=localhost:6790&addr=localhost:6791 -// is equivalent to: -// &ClusterOptions{ -// Addr: ["localhost:6789", "localhost:6790", "localhost:6791"] -// DialTimeout: 3 * time.Second, // no time unit = seconds -// ReadTimeout: 6 * time.Second, -// } +// +// redis://user:password@localhost:6789?dial_timeout=3&read_timeout=6s&addr=localhost:6790&addr=localhost:6791 +// is equivalent to: +// &ClusterOptions{ +// Addr: ["localhost:6789", "localhost:6790", "localhost:6791"] +// DialTimeout: 3 * time.Second, // no time unit = seconds +// ReadTimeout: 6 * time.Second, +// } func ParseClusterURL(redisURL string) (*ClusterOptions, error) { o := &ClusterOptions{} diff --git a/command.go b/command.go index 59cd8a6c..8c77c24d 100644 --- a/command.go +++ b/command.go @@ -1110,15 +1110,16 @@ func (cmd *KeyValueSliceCmd) String() string { } // Many commands will respond to two formats: -// 1) 1) "one" -// 2) (double) 1 -// 2) 1) "two" -// 2) (double) 2 +// 1. 1) "one" +// 2. (double) 1 +// 2. 1) "two" +// 2. (double) 2 +// // OR: -// 1) "two" -// 2) (double) 2 -// 3) "one" -// 4) (double) 1 +// 1. "two" +// 2. (double) 2 +// 3. "one" +// 4. (double) 1 func (cmd *KeyValueSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl n, err := rd.ReadArrayLen() if err != nil { diff --git a/internal/once.go b/internal/once.go index 64f46272..b81244fd 100644 --- a/internal/once.go +++ b/internal/once.go @@ -32,7 +32,9 @@ type Once struct { // Do calls the function f if and only if Do has not been invoked // without error for this instance of Once. In other words, given -// var once Once +// +// var once Once +// // if once.Do(f) is called multiple times, only the first call will // invoke f, even if f has a different value in each invocation unless // f returns an error. A new instance of Once is required for each @@ -41,7 +43,8 @@ type Once struct { // Do is intended for initialization that must be run exactly once. Since f // is niladic, it may be necessary to use a function literal to capture the // arguments to a function to be invoked by Do: -// err := config.once.Do(func() error { return config.init(filename) }) +// +// err := config.once.Do(func() error { return config.init(filename) }) func (o *Once) Do(f func() error) error { if atomic.LoadUint32(&o.done) == 1 { return nil diff --git a/internal/proto/scan.go b/internal/proto/scan.go index 576120c5..f30f3baf 100644 --- a/internal/proto/scan.go +++ b/internal/proto/scan.go @@ -11,6 +11,7 @@ import ( ) // Scan parses bytes `b` to `v` with appropriate type. +// //nolint:gocyclo func Scan(b []byte, v interface{}) error { switch v := v.(type) { diff --git a/options.go b/options.go index 49abe214..6a625bfa 100644 --- a/options.go +++ b/options.go @@ -223,32 +223,38 @@ func NewDialer(opt *Options) func(context.Context, string, string) (net.Conn, er // Scheme is required. // There are two connection types: by tcp socket and by unix socket. // Tcp connection: -// redis://:@:/ +// +// redis://:@:/ +// // Unix connection: -// unix://:@?db= +// +// unix://:@?db= +// // Most Option fields can be set using query parameters, with the following restrictions: -// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries -// - only scalar type fields are supported (bool, int, time.Duration) -// - for time.Duration fields, values must be a valid input for time.ParseDuration(); -// additionally a plain integer as value (i.e. without unit) is intepreted as seconds -// - to disable a duration field, use value less than or equal to 0; to use the default -// value, leave the value blank or remove the parameter -// - only the last value is interpreted if a parameter is given multiple times -// - fields "network", "addr", "username" and "password" can only be set using other -// URL attributes (scheme, host, userinfo, resp.), query paremeters using these -// names will be treated as unknown parameters -// - unknown parameter names will result in an error +// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries +// - only scalar type fields are supported (bool, int, time.Duration) +// - for time.Duration fields, values must be a valid input for time.ParseDuration(); +// additionally a plain integer as value (i.e. without unit) is intepreted as seconds +// - to disable a duration field, use value less than or equal to 0; to use the default +// value, leave the value blank or remove the parameter +// - only the last value is interpreted if a parameter is given multiple times +// - fields "network", "addr", "username" and "password" can only be set using other +// URL attributes (scheme, host, userinfo, resp.), query paremeters using these +// names will be treated as unknown parameters +// - unknown parameter names will result in an error +// // Examples: -// redis://user:password@localhost:6789/3?dial_timeout=3&db=1&read_timeout=6s&max_retries=2 -// is equivalent to: -// &Options{ -// Network: "tcp", -// Addr: "localhost:6789", -// DB: 1, // path "/3" was overridden by "&db=1" -// DialTimeout: 3 * time.Second, // no time unit = seconds -// ReadTimeout: 6 * time.Second, -// MaxRetries: 2, -// } +// +// redis://user:password@localhost:6789/3?dial_timeout=3&db=1&read_timeout=6s&max_retries=2 +// is equivalent to: +// &Options{ +// Network: "tcp", +// Addr: "localhost:6789", +// DB: 1, // path "/3" was overridden by "&db=1" +// DialTimeout: 3 * time.Second, // no time unit = seconds +// ReadTimeout: 6 * time.Second, +// MaxRetries: 2, +// } func ParseURL(redisURL string) (*Options, error) { u, err := url.Parse(redisURL) if err != nil { diff --git a/redis.go b/redis.go index 8c83788d..9fe0cd1a 100644 --- a/redis.go +++ b/redis.go @@ -29,9 +29,9 @@ func SetLogger(logger internal.Logging) { //------------------------------------------------------------------------------ type Hook interface { - DialHook(hook DialHook) DialHook - ProcessHook(hook ProcessHook) ProcessHook - ProcessPipelineHook(hook ProcessPipelineHook) ProcessPipelineHook + DialHook(next DialHook) DialHook + ProcessHook(next ProcessHook) ProcessHook + ProcessPipelineHook(next ProcessPipelineHook) ProcessPipelineHook } type ( @@ -48,6 +48,43 @@ type hooks struct { processTxPipelineHook ProcessPipelineHook } +// AddHook is to add a hook to the queue. +// Hook is a function executed during network connection, command execution, and pipeline, +// it is a first-in-last-out stack queue (FILO). +// The first to be added to the queue is the execution function of the redis command (the last to be executed). +// You need to execute the next hook in each hook, unless you want to terminate the execution of the command. +// For example, you added hook-1, hook-2: +// +// client.AddHook(hook-1, hook-2) +// +// hook-1: +// +// func (Hook1) ProcessHook(next redis.ProcessHook) redis.ProcessHook { +// return func(ctx context.Context, cmd Cmder) error { +// print("hook-1 start") +// next(ctx, cmd) +// print("hook-1 end") +// return nil +// } +// } +// +// hook-2: +// +// func (Hook2) ProcessHook(next redis.ProcessHook) redis.ProcessHook { +// return func(ctx context.Context, cmd redis.Cmder) error { +// print("hook-2 start") +// next(ctx, cmd) +// print("hook-2 end") +// return nil +// } +// } +// +// The execution sequence is: +// +// hook-2 start -> hook-1 start -> exec redis cmd -> hook-1 end -> hook-2 end +// +// Please note: "next(ctx, cmd)" is very important, it will call the next hook, +// if "next(ctx, cmd)" is not executed in hook-1, the redis command will not be executed. func (hs *hooks) AddHook(hook Hook) { hs.slice = append(hs.slice, hook) hs.dialHook = hook.DialHook(hs.dialHook) @@ -575,7 +612,7 @@ func (c *Client) Conn() *Conn { return newConn(c.opt, pool.NewStickyConnPool(c.connPool)) } -// Do creates a Cmd from the args and processes the cmd. +// Do create a Cmd from the args and processes the cmd. func (c *Client) Do(ctx context.Context, args ...interface{}) *Cmd { cmd := NewCmd(ctx, args...) _ = c.Process(ctx, cmd) @@ -648,26 +685,26 @@ func (c *Client) pubSub() *PubSub { // subscription may not be active immediately. To force the connection to wait, // you may call the Receive() method on the returned *PubSub like so: // -// sub := client.Subscribe(queryResp) -// iface, err := sub.Receive() -// if err != nil { -// // handle error -// } +// sub := client.Subscribe(queryResp) +// iface, err := sub.Receive() +// if err != nil { +// // handle error +// } // -// // Should be *Subscription, but others are possible if other actions have been -// // taken on sub since it was created. -// switch iface.(type) { -// case *Subscription: -// // subscribe succeeded -// case *Message: -// // received first message -// case *Pong: -// // pong received -// default: -// // handle error -// } +// // Should be *Subscription, but others are possible if other actions have been +// // taken on sub since it was created. +// switch iface.(type) { +// case *Subscription: +// // subscribe succeeded +// case *Message: +// // received first message +// case *Pong: +// // pong received +// default: +// // handle error +// } // -// ch := sub.Channel() +// ch := sub.Channel() func (c *Client) Subscribe(ctx context.Context, channels ...string) *PubSub { pubsub := c.pubSub() if len(channels) > 0 {