github.com/hdt3213/godis@v1.2.9/database/transaction.go (about) 1 package database 2 3 import ( 4 "github.com/hdt3213/godis/interface/redis" 5 "github.com/hdt3213/godis/redis/protocol" 6 "strings" 7 ) 8 9 // Watch set watching keys 10 func Watch(db *DB, conn redis.Connection, args [][]byte) redis.Reply { 11 watching := conn.GetWatching() 12 for _, bkey := range args { 13 key := string(bkey) 14 watching[key] = db.GetVersion(key) 15 } 16 return protocol.MakeOkReply() 17 } 18 19 func execGetVersion(db *DB, args [][]byte) redis.Reply { 20 key := string(args[0]) 21 ver := db.GetVersion(key) 22 return protocol.MakeIntReply(int64(ver)) 23 } 24 25 func init() { 26 registerCommand("GetVer", execGetVersion, readAllKeys, nil, 2, flagReadOnly) 27 } 28 29 // invoker should lock watching keys 30 func isWatchingChanged(db *DB, watching map[string]uint32) bool { 31 for key, ver := range watching { 32 currentVersion := db.GetVersion(key) 33 if ver != currentVersion { 34 return true 35 } 36 } 37 return false 38 } 39 40 // StartMulti starts multi-command-transaction 41 func StartMulti(conn redis.Connection) redis.Reply { 42 if conn.InMultiState() { 43 return protocol.MakeErrReply("ERR MULTI calls can not be nested") 44 } 45 conn.SetMultiState(true) 46 return protocol.MakeOkReply() 47 } 48 49 // EnqueueCmd puts command line into `multi` pending queue 50 func EnqueueCmd(conn redis.Connection, cmdLine [][]byte) redis.Reply { 51 cmdName := strings.ToLower(string(cmdLine[0])) 52 cmd, ok := cmdTable[cmdName] 53 if !ok { 54 err := protocol.MakeErrReply("ERR unknown command '" + cmdName + "'") 55 conn.AddTxError(err) 56 return err 57 } 58 if cmd.prepare == nil { 59 err := protocol.MakeErrReply("ERR command '" + cmdName + "' cannot be used in MULTI") 60 conn.AddTxError(err) 61 return err 62 } 63 if !validateArity(cmd.arity, cmdLine) { 64 err := protocol.MakeArgNumErrReply(cmdName) 65 conn.AddTxError(err) 66 return err 67 } 68 conn.EnqueueCmd(cmdLine) 69 return protocol.MakeQueuedReply() 70 } 71 72 func execMulti(db *DB, conn redis.Connection) redis.Reply { 73 if !conn.InMultiState() { 74 return protocol.MakeErrReply("ERR EXEC without MULTI") 75 } 76 defer conn.SetMultiState(false) 77 if len(conn.GetTxErrors()) > 0 { 78 return protocol.MakeErrReply("EXECABORT Transaction discarded because of previous errors.") 79 } 80 cmdLines := conn.GetQueuedCmdLine() 81 return db.ExecMulti(conn, conn.GetWatching(), cmdLines) 82 } 83 84 // ExecMulti executes multi commands transaction Atomically and Isolated 85 func (db *DB) ExecMulti(conn redis.Connection, watching map[string]uint32, cmdLines []CmdLine) redis.Reply { 86 // prepare 87 writeKeys := make([]string, 0) // may contains duplicate 88 readKeys := make([]string, 0) 89 for _, cmdLine := range cmdLines { 90 cmdName := strings.ToLower(string(cmdLine[0])) 91 cmd := cmdTable[cmdName] 92 prepare := cmd.prepare 93 write, read := prepare(cmdLine[1:]) 94 writeKeys = append(writeKeys, write...) 95 readKeys = append(readKeys, read...) 96 } 97 // set watch 98 watchingKeys := make([]string, 0, len(watching)) 99 for key := range watching { 100 watchingKeys = append(watchingKeys, key) 101 } 102 readKeys = append(readKeys, watchingKeys...) 103 db.RWLocks(writeKeys, readKeys) 104 defer db.RWUnLocks(writeKeys, readKeys) 105 106 if isWatchingChanged(db, watching) { // watching keys changed, abort 107 return protocol.MakeEmptyMultiBulkReply() 108 } 109 // execute 110 results := make([]redis.Reply, 0, len(cmdLines)) 111 aborted := false 112 undoCmdLines := make([][]CmdLine, 0, len(cmdLines)) 113 for _, cmdLine := range cmdLines { 114 undoCmdLines = append(undoCmdLines, db.GetUndoLogs(cmdLine)) 115 result := db.execWithLock(cmdLine) 116 if protocol.IsErrorReply(result) { 117 aborted = true 118 // don't rollback failed commands 119 undoCmdLines = undoCmdLines[:len(undoCmdLines)-1] 120 break 121 } 122 results = append(results, result) 123 } 124 if !aborted { //success 125 db.addVersion(writeKeys...) 126 return protocol.MakeMultiRawReply(results) 127 } 128 // undo if aborted 129 size := len(undoCmdLines) 130 for i := size - 1; i >= 0; i-- { 131 curCmdLines := undoCmdLines[i] 132 if len(curCmdLines) == 0 { 133 continue 134 } 135 for _, cmdLine := range curCmdLines { 136 db.execWithLock(cmdLine) 137 } 138 } 139 return protocol.MakeErrReply("EXECABORT Transaction discarded because of previous errors.") 140 } 141 142 // DiscardMulti drops MULTI pending commands 143 func DiscardMulti(conn redis.Connection) redis.Reply { 144 if !conn.InMultiState() { 145 return protocol.MakeErrReply("ERR DISCARD without MULTI") 146 } 147 conn.ClearQueuedCmds() 148 conn.SetMultiState(false) 149 return protocol.MakeOkReply() 150 } 151 152 // GetUndoLogs return rollback commands 153 func (db *DB) GetUndoLogs(cmdLine [][]byte) []CmdLine { 154 cmdName := strings.ToLower(string(cmdLine[0])) 155 cmd, ok := cmdTable[cmdName] 156 if !ok { 157 return nil 158 } 159 undo := cmd.undo 160 if undo == nil { 161 return nil 162 } 163 return undo(db, cmdLine[1:]) 164 } 165 166 // GetRelatedKeys analysis related keys 167 func GetRelatedKeys(cmdLine [][]byte) ([]string, []string) { 168 cmdName := strings.ToLower(string(cmdLine[0])) 169 cmd, ok := cmdTable[cmdName] 170 if !ok { 171 return nil, nil 172 } 173 prepare := cmd.prepare 174 if prepare == nil { 175 return nil, nil 176 } 177 return prepare(cmdLine[1:]) 178 }