1// Copyright 2013 Gary Burd
2//
3// Licensed under the Apache License, Version 2.0 (the "License"): you may
4// not use this file except in compliance with the License. You may obtain
5// a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations
13// under the License.
14
15package redis_test
16
17import (
18	"fmt"
19	"github.com/garyburd/redigo/redis"
20)
21
22// zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands.
23func zpop(c redis.Conn, key string) (result string, err error) {
24
25	defer func() {
26		// Return connection to normal state on error.
27		if err != nil {
28			c.Do("DISCARD")
29		}
30	}()
31
32	// Loop until transaction is successful.
33	for {
34		if _, err := c.Do("WATCH", key); err != nil {
35			return "", err
36		}
37
38		members, err := redis.Strings(c.Do("ZRANGE", key, 0, 0))
39		if err != nil {
40			return "", err
41		}
42		if len(members) != 1 {
43			return "", redis.ErrNil
44		}
45
46		c.Send("MULTI")
47		c.Send("ZREM", key, members[0])
48		queued, err := c.Do("EXEC")
49		if err != nil {
50			return "", err
51		}
52
53		if queued != nil {
54			result = members[0]
55			break
56		}
57	}
58
59	return result, nil
60}
61
62// zpopScript pops a value from a ZSET.
63var zpopScript = redis.NewScript(1, `
64    local r = redis.call('ZRANGE', KEYS[1], 0, 0)
65    if r ~= nil then
66        r = r[1]
67        redis.call('ZREM', KEYS[1], r)
68    end
69    return r
70`)
71
72// This example implements ZPOP as described at
73// http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting.
74func Example_zpop() {
75	c, err := dial()
76	if err != nil {
77		fmt.Println(err)
78		return
79	}
80	defer c.Close()
81
82	// Add test data using a pipeline.
83
84	for i, member := range []string{"red", "blue", "green"} {
85		c.Send("ZADD", "zset", i, member)
86	}
87	if _, err := c.Do(""); err != nil {
88		fmt.Println(err)
89		return
90	}
91
92	// Pop using WATCH/MULTI/EXEC
93
94	v, err := zpop(c, "zset")
95	if err != nil {
96		fmt.Println(err)
97		return
98	}
99	fmt.Println(v)
100
101	// Pop using a script.
102
103	v, err = redis.String(zpopScript.Do(c, "zset"))
104	if err != nil {
105		fmt.Println(err)
106		return
107	}
108	fmt.Println(v)
109
110	// Output:
111	// red
112	// blue
113}
114