package redis import ( "github.com/go-redis/redis/internal" "github.com/go-redis/redis/internal/pool" ) // TxFailedErr transaction redis failed. const TxFailedErr = internal.RedisError("redis: transaction failed") // Tx implements Redis transactions as described in // http://redis.io/topics/transactions. It's NOT safe for concurrent use // by multiple goroutines, because Exec resets list of watched keys. // If you don't need WATCH it is better to use Pipeline. type Tx struct { statefulCmdable baseClient } func (c *Client) newTx() *Tx { tx := Tx{ baseClient: baseClient{ opt: c.opt, connPool: pool.NewStickyConnPool(c.connPool.(*pool.ConnPool), true), }, } tx.baseClient.init() tx.statefulCmdable.setProcessor(tx.Process) return &tx } func (c *Client) Watch(fn func(*Tx) error, keys ...string) error { tx := c.newTx() if len(keys) > 0 { if err := tx.Watch(keys...).Err(); err != nil { _ = tx.Close() return err } } err := fn(tx) _ = tx.Close() return err } // Close closes the transaction, releasing any open resources. func (c *Tx) Close() error { _ = c.Unwatch().Err() return c.baseClient.Close() } // Watch marks the keys to be watched for conditional execution // of a transaction. func (c *Tx) Watch(keys ...string) *StatusCmd { args := make([]interface{}, 1+len(keys)) args[0] = "watch" for i, key := range keys { args[1+i] = key } cmd := NewStatusCmd(args...) c.Process(cmd) return cmd } // Unwatch flushes all the previously watched keys for a transaction. func (c *Tx) Unwatch(keys ...string) *StatusCmd { args := make([]interface{}, 1+len(keys)) args[0] = "unwatch" for i, key := range keys { args[1+i] = key } cmd := NewStatusCmd(args...) c.Process(cmd) return cmd } func (c *Tx) Pipeline() Pipeliner { pipe := Pipeline{ exec: c.processTxPipeline, } pipe.statefulCmdable.setProcessor(pipe.Process) return &pipe } // Pipelined executes commands queued in the fn in a transaction // and restores the connection state to normal. // // When using WATCH, EXEC will execute commands only if the watched keys // were not modified, allowing for a check-and-set mechanism. // // Exec always returns list of commands. If transaction fails // TxFailedErr is returned. Otherwise Exec returns error of the first // failed command or nil. func (c *Tx) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.Pipeline().Pipelined(fn) } func (c *Tx) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.Pipelined(fn) } func (c *Tx) TxPipeline() Pipeliner { return c.Pipeline() }