mirror of
https://github.com/HDT3213/godis.git
synced 2025-10-11 03:30:55 +08:00
support multi transaction
This commit is contained in:
94
multi.go
Normal file
94
multi.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package godis
|
||||
|
||||
import (
|
||||
"github.com/hdt3213/godis/interface/redis"
|
||||
"github.com/hdt3213/godis/redis/reply"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func startMulti(db *DB, conn redis.Connection) redis.Reply {
|
||||
if conn.InMultiState() {
|
||||
return reply.MakeErrReply("ERR MULTI calls can not be nested")
|
||||
}
|
||||
conn.SetMultiState(true)
|
||||
return reply.MakeOkReply()
|
||||
}
|
||||
|
||||
func enqueueCmd(db *DB, conn redis.Connection, cmdLine [][]byte) redis.Reply {
|
||||
cmdName := strings.ToLower(string(cmdLine[0]))
|
||||
cmd, ok := cmdTable[cmdName]
|
||||
if !ok {
|
||||
return reply.MakeErrReply("ERR unknown command '" + cmdName + "'")
|
||||
}
|
||||
if cmd.prepare == nil {
|
||||
return reply.MakeErrReply("ERR command '" + cmdName + "' cannot be used in MULTI")
|
||||
}
|
||||
if !validateArity(cmd.arity, cmdLine) {
|
||||
// difference with redis: we won't enqueue command line with wrong arity
|
||||
return reply.MakeArgNumErrReply(cmdName)
|
||||
}
|
||||
conn.EnqueueCmd(cmdLine)
|
||||
return reply.MakeQueuedReply()
|
||||
}
|
||||
|
||||
func execMulti(db *DB, conn redis.Connection) redis.Reply {
|
||||
if !conn.InMultiState() {
|
||||
return reply.MakeErrReply("ERR EXEC without MULTI")
|
||||
}
|
||||
defer conn.SetMultiState(false)
|
||||
cmdLines := conn.GetQueuedCmdLine()
|
||||
|
||||
// prepare
|
||||
writeKeys := make([]string, 0) // may contains duplicate
|
||||
readKeys := make([]string, 0)
|
||||
for _, cmdLine := range cmdLines {
|
||||
cmdName := strings.ToLower(string(cmdLine[0]))
|
||||
cmd := cmdTable[cmdName]
|
||||
prepare := cmd.prepare
|
||||
write, read := prepare(cmdLine[1:])
|
||||
writeKeys = append(writeKeys, write...)
|
||||
readKeys = append(readKeys, read...)
|
||||
}
|
||||
db.RWLocks(writeKeys, readKeys)
|
||||
defer db.RWUnLocks(writeKeys, readKeys)
|
||||
|
||||
// execute
|
||||
results := make([][]byte, 0, len(cmdLines))
|
||||
aborted := false
|
||||
undoCmdLines := make([][]CmdLine, 0, len(cmdLines))
|
||||
for _, cmdLine := range cmdLines {
|
||||
undoCmdLines = append(undoCmdLines, db.GetUndoLogs(cmdLine))
|
||||
result := db.ExecWithLock(cmdLine)
|
||||
if reply.IsErrorReply(result) {
|
||||
aborted = true
|
||||
// don't rollback failed commands
|
||||
undoCmdLines = undoCmdLines[:len(undoCmdLines)-1]
|
||||
break
|
||||
}
|
||||
results = append(results, result.ToBytes())
|
||||
}
|
||||
if !aborted {
|
||||
return reply.MakeMultiRawReply(results)
|
||||
}
|
||||
// undo if aborted
|
||||
size := len(undoCmdLines)
|
||||
for i := size - 1; i >= 0; i-- {
|
||||
curCmdLines := undoCmdLines[i]
|
||||
if len(curCmdLines) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, cmdLine := range curCmdLines {
|
||||
db.ExecWithLock(cmdLine)
|
||||
}
|
||||
}
|
||||
return reply.MakeErrReply("EXECABORT Transaction discarded because of previous errors.")
|
||||
}
|
||||
|
||||
func discardMulti(db *DB, conn redis.Connection) redis.Reply {
|
||||
if !conn.InMultiState() {
|
||||
return reply.MakeErrReply("ERR DISCARD without MULTI")
|
||||
}
|
||||
conn.ClearQueuedCmds()
|
||||
conn.SetMultiState(false)
|
||||
return reply.MakeQueuedReply()
|
||||
}
|
Reference in New Issue
Block a user