Add support for brpoplpush and rpoplpush commands (#295)

This commit is contained in:
Muhamad Azmy 2017-05-18 14:07:40 +02:00 committed by siddontang
parent 5835ab9b2b
commit 0b1b6de120
3 changed files with 160 additions and 0 deletions

View File

@ -61,12 +61,14 @@ Most of the Ledisdb's commands are the same as Redis's, you can see the redis co
- [List](#list)
- [BLPOP key [key ...] timeout](#blpop-key-key--timeout)
- [BRPOP key [key ...] timeout](#brpop-key-key--timeout)
- [BRPOPLPUSH source destination timeout](#brpoplpush-source-destination-timeout)
- [LINDEX key index](#lindex-key-index)
- [LLEN key](#llen-key)
- [LPOP key](#lpop-key)
- [LRANGE key start stop](#lrange-key-start-stop)
- [LPUSH key value [value ...]](#lpush-key-value-value-)
- [RPOP key](#rpop-key)
- [RPOPLPUSH source destination](#rpoplpush-source-destination)
- [RPUSH key value [value ...]](#rpush-key-value-value-)
- [LCLEAR key](#lclear-key)
- [LMCLEAR key [key ...]](#lmclear-key-key-)
@ -950,6 +952,12 @@ ledis> BLPOP list1 list2 0
See [BLPOP key [key ...] timeout](#blpop-key-key--timeout) for more information.
### BRPOPLPUSH source destination timeout
BRPOPLPUSH is the blocking variant of [RPOPLPUSH](#rpoplpush-source-destination).
When source contains elements, this command behaves exactly like RPOPLPUSH.
Redis will block the connection until another client pushes to it or until timeout is reached.
A timeout of zero can be used to block indefinitely.
### LINDEX key index
Returns the element at index index in the list stored at key. The index is zero-based, so 0 means the first element, 1 the second element and so on. Negative indices can be used to designate elements starting at the tail of the list. Here, `-1` means the last element, `-2` means the penultimate and so forth.
When the value at key is not a list, an error is returned.
@ -1081,6 +1089,14 @@ ledis> LRANGE a 0 3
2) "2"
```
### RPOPLPUSH source destination
Atomically returns and removes the last element (tail) of the list stored at source, and pushes the element at the first element (head) of the list stored at destination.
For example: consider source holding the list a,b,c, and destination holding the list x,y,z. Executing RPOPLPUSH results in source holding a,b and destination holding c,x,y,z.
If source does not exist, the value nil is returned and no operation is performed.
If source and destination are the same, the operation is equivalent to removing the last element from the list and pushing it as first element of the list, so it can be considered as a list rotation command.
### RPUSH key value [value ...]
Insert all the specified values at the tail of the list stored at key. If key does not exist, it is created as empty list before performing the push operation. When key holds a value that is not a list, an error is returned.

View File

@ -279,6 +279,82 @@ func lParseBPopArgs(c *client) (keys [][]byte, timeout time.Duration, err error)
return
}
func brpoplpushCommand(c *client) error {
source, dest, timeout, err := lParseBRPoplpushArgs(c)
if err != nil {
return err
}
ay, err := c.db.BRPop([][]byte{source}, timeout)
if err != nil {
return err
}
if ay == nil {
c.resp.writeBulk(nil)
return nil
}
data, ok := ay[1].([]byte)
if !ok {
//not sure if this even possible
return ErrValue
}
if _, err := c.db.LPush(dest, data); err != nil {
c.db.RPush(source, data) //revert pop
return err
}
c.resp.writeBulk(data)
return nil
}
func lParseBRPoplpushArgs(c *client) (source []byte, dest []byte, timeout time.Duration, err error) {
args := c.args
if len(args) != 3 {
err = ErrCmdParams
return
}
source = args[0]
dest = args[1]
var t float64
if t, err = strconv.ParseFloat(hack.String(args[len(args)-1]), 64); err != nil {
return
}
timeout = time.Duration(t * float64(time.Second))
return
}
func rpoplpushCommand(c *client) error {
args := c.args
if len(args) != 2 {
return ErrCmdParams
}
source, dest := args[0], args[1]
data, err := c.db.RPop(source)
if err != nil {
return err
}
if data == nil {
c.resp.writeBulk(nil)
return nil
}
if _, err := c.db.LPush(dest, data); err != nil {
c.db.RPush(source, data) //revert pop
return err
}
c.resp.writeBulk(data)
return nil
}
func lkeyexistsCommand(c *client) error {
args := c.args
if len(args) != 1 {
@ -376,6 +452,8 @@ func init() {
register("lpush", lpushCommand)
register("rpop", rpopCommand)
register("rpush", rpushCommand)
register("brpoplpush", brpoplpushCommand)
register("rpoplpush", rpoplpushCommand)
//ledisdb special command

View File

@ -297,6 +297,72 @@ func TestPop(t *testing.T) {
}
func TestRPopLPush(t *testing.T) {
c := getTestConn()
defer c.Close()
src := []byte("sr")
des := []byte("de")
if _, err := goredis.Int(c.Do("rpoplpush", src, des)); err != goredis.ErrNil {
t.Fatal(err)
}
if v, err := goredis.Int(c.Do("llen", des)); err != nil {
t.Fatal(err)
} else if v != 0 {
t.Fatal(v)
}
if n, err := goredis.Int(c.Do("rpush", src, 1, 2, 3, 4, 5, 6)); err != nil {
t.Fatal(err)
} else if n != 6 {
t.Fatal(n)
}
if v, err := goredis.Int(c.Do("rpoplpush", src, src)); err != nil {
t.Fatal(err)
} else if v != 6 {
t.Fatal(v)
}
if v, err := goredis.Int(c.Do("llen", src)); err != nil {
t.Fatal(err)
} else if v != 6 {
t.Fatal(v)
}
if v, err := goredis.Int(c.Do("rpoplpush", src, des)); err != nil {
t.Fatal(err)
} else if v != 5 {
t.Fatal(v)
}
if v, err := goredis.Int(c.Do("llen", src)); err != nil {
t.Fatal(err)
} else if v != 5 {
t.Fatal(v)
}
if v, err := goredis.Int(c.Do("llen", des)); err != nil {
t.Fatal(err)
} else if v != 1 {
t.Fatal(v)
}
if v, err := goredis.Int(c.Do("lpop", des)); err != nil {
t.Fatal(err)
} else if v != 5 {
t.Fatal(v)
}
if v, err := goredis.Int(c.Do("lpop", src)); err != nil {
t.Fatal(err)
} else if v != 6 {
t.Fatal(v)
}
}
func TestTrim(t *testing.T) {
c := getTestConn()
defer c.Close()