Skip to content

Commit

Permalink
DiceDB#1128: Added support for ZPOPMIN (DiceDB#1143)
Browse files Browse the repository at this point in the history
  • Loading branch information
surya0180 authored Oct 19, 2024
1 parent 91f3d2b commit 045376e
Show file tree
Hide file tree
Showing 11 changed files with 740 additions and 1 deletion.
148 changes: 148 additions & 0 deletions docs/src/content/docs/commands/ZPOPMIN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
title: ZPOPMIN
description: The `ZPOPMIN` command in DiceDB is used to remove and return the members with the lowest scores from the sorted set data structure at the specified key. If a count is provided, it returns up to that number of members with the lowest scores, removing them from the set.
---

The `ZPOPMIN` command in DiceDB is used to remove and return the members with the lowest scores from the sorted set data structure at the specified key. If a count is provided, it returns up to that number of members with the lowest scores, removing them from the set.

## Syntax

```
ZPOPMIN key [count]
```

## Parameters

| Parameter | Description | Type | Required |
|------------|----------------------------------------------------------------------------------------------|---------|----------|
| `key` | The name of the sorted set data structure. If it does not exist, an empty array is returned. | String | Yes |
| `count` | The count argument specifies the maximum number of members to return with the lowest scores. | Integer | No |

## Return values

| Condition | Return Value |
|----------------------------------------------------------|------------------------------------------|
| If the key is of valid type and records are present | List of members including their scores |
| If the key does not exist or if the sorted set is empty | `(empty list or set)` |

## Behaviour

- The command first checks if the specified key exists.
- If the key does not exist, an empty array is returned.
- If the key exists but is not a sorted set, an error is returned.
- If the `count` argument is specified, up to that number of members with the lowest scores are returned and removed.
- The returned array contains the members and their corresponding scores in the order of lowest to highest.

## Errors
1. `Wrong type error`:
- Error Message: `(error) WRONGTYPE Operation against a key holding the wrong kind of value`
- Occurs when trying to use the command on a key that is not a sorted set.

2. `Syntax error`:
- Error Message: `(error) ERROR wrong number of arguments for 'zpopmin' command`
- Occurs when the command syntax is incorrect or missing required parameters.

3. `Invalid argument type error`:
- Error Message : `(error) ERR value is not an integer or out of range`
- Occurs when the count argument passed to the command is not an integer.

## Examples

### Non-Existing Key (without count argument)

Attempting to pop the member with the lowest score from a non-existent sorted set:

```bash
127.0.0.1:7379> ZPOPMIN NON_EXISTENT_KEY
(empty array)
```

### Existing Key (without count argument)

Popping the member with the lowest score from an existing sorted set:

```bash
127.0.0.1:7379> ZADD myzset 1 member1 2 member2 3 member3
(integer) 3
127.0.0.1:7379> ZPOPMIN myzset
1) 1 "member1"
```

### With Count Argument

Popping multiple members with the lowest scores using the count argument:

```bash
127.0.0.1:7379> ZADD myzset 1 member1 2 member2 3 member3
(integer) 3
127.0.0.1:7379> ZPOPMIN myzset 2
1) 1 "member1"
2) 2 "member2"
```

### Count Argument but Multiple Members Have the Same Score

Popping members when multiple members share the same score:

```bash
127.0.0.1:7379> ZADD myzset 1 member1 1 member2 1 member3
(integer) 3
127.0.0.1:7379> ZPOPMIN myzset 2
1) 1 "member1"
2) 1 "member2"
```

### Negative Count Argument

Attempting to pop members using a negative count argument:

```bash
127.0.0.1:7379> ZADD myzset 1 member1 2 member2 3 member3
(integer) 3
127.0.0.1:7379> ZPOPMIN myzset -1
(empty array)
```

### Floating-Point Scores

Popping members with floating-point scores:

```bash
127.0.0.1:7379> ZADD myzset 1.5 member1 2.7 member2 3.8 member3
(integer) 3
127.0.0.1:7379> ZPOPMIN myzset
1) 1.5 "member1"
```

### Wrong number of arguments

Attempting to pop from a key that is not a sorted set:

```bash
127.0.0.1:7379> SET stringkey "string_value"
OK
127.0.0.1:7379> ZPOPMIN stringkey
(error) WRONGTYPE Operation against a key holding the wrong kind of value
```

### Invalid Count Argument

Using an invalid (non-integer) count argument:

```bash
127.0.0.1:7379> ZADD myzset 1 member1
(integer) 1
127.0.0.1:7379> ZPOPMIN myzset INCORRECT_COUNT_ARGUMENT
(error) ERR value is not an integer or out of range
```

### Wrong Type of Key (without count argument)

Attempting to pop from a key that is not a sorted set:

```bash
127.0.0.1:7379> SET stringkey "string_value"
OK
127.0.0.1:7379> ZPOPMIN stringkey
(error) WRONGTYPE Operation against a key holding the wrong kind of value
```
70 changes: 69 additions & 1 deletion integration_tests/commands/async/zset_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package async

import (
"gotest.tools/v3/assert"
"testing"

"gotest.tools/v3/assert"
)

func TestZADD(t *testing.T) {
Expand Down Expand Up @@ -117,3 +118,70 @@ func TestZRANGE(t *testing.T) {
})
}
}

func TestZPOPMIN(t *testing.T) {
conn := getLocalConnection()
defer conn.Close()

testCases := []TestCase{
{
name: "ZPOPMIN on non-existing key with/without count argument",
commands: []string{"ZPOPMIN NON_EXISTENT_KEY"},
expected: []interface{}{[]interface{}{}},
},
{
name: "ZPOPMIN with wrong type of key with/without count argument",
commands: []string{"SET stringkey string_value", "ZPOPMIN stringkey", "DEL stringkey"},
expected: []interface{}{"OK", "WRONGTYPE Operation against a key holding the wrong kind of value", int64(1)},
},
{
name: "ZPOPMIN on existing key (without count argument)",
commands: []string{"ZADD myzset 1 member1 2 member2 3 member3", "ZPOPMIN myzset", "DEL myzset"},
expected: []interface{}{int64(3), []interface{}{"member1", "1"}, int64(1)},
},
{
name: "ZPOPMIN with normal count argument",
commands: []string{"ZADD myzset 1 member1 2 member2 3 member3", "ZPOPMIN myzset 2", "DEL myzset"},
expected: []interface{}{int64(3), []interface{}{"member1", "1", "member2", "2"}, int64(1)},
},
{
name: "ZPOPMIN with count argument but multiple members have the same score",
commands: []string{"ZADD myzset 1 member1 1 member2 1 member3", "ZPOPMIN myzset 2", "DEL myzset"},
expected: []interface{}{int64(3), []interface{}{"member1", "1", "member2", "1"}, int64(1)},
},
{
name: "ZPOPMIN with negative count argument",
commands: []string{"ZADD myzset 1 member1 2 member2 3 member3", "ZPOPMIN myzset -1", "DEL myzset"},
expected: []interface{}{int64(3), []interface{}{}, int64(1)},
},
{
name: "ZPOPMIN with invalid count argument",
commands: []string{"ZADD myzset 1 member1", "ZPOPMIN myzset INCORRECT_COUNT_ARGUMENT", "DEL myzset"},
expected: []interface{}{int64(1), "ERR value is not an integer or out of range", int64(1)},
},
{
name: "ZPOPMIN with count argument greater than length of sorted set",
commands: []string{"ZADD myzset 1 member1 2 member2 3 member3", "ZPOPMIN myzset 10", "DEL myzset"},
expected: []interface{}{int64(3), []interface{}{"member1", "1", "member2", "2", "member3", "3"}, int64(1)},
},
{
name: "ZPOPMIN on empty sorted set",
commands: []string{"ZADD myzset 1 member1", "ZPOPMIN myzset 1", "ZPOPMIN myzset", "DEL myzset"},
expected: []interface{}{int64(1), []interface{}{"member1", "1"}, []interface{}{}, int64(1)},
},
{
name: "ZPOPMIN with floating-point scores",
commands: []string{"ZADD myzset 1.5 member1 2.7 member2 3.8 member3", "ZPOPMIN myzset", "DEL myzset"},
expected: []interface{}{int64(3), []interface{}{"member1", "1.5"}, int64(1)},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for i, cmd := range tc.commands {
result := FireCommand(conn, cmd)
assert.DeepEqual(t, tc.expected[i], result)
}
})
}
}
107 changes: 107 additions & 0 deletions integration_tests/commands/http/zset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package http

import (
"testing"

"gotest.tools/v3/assert"
)

func TestZPOPMIN(t *testing.T) {
exec := NewHTTPCommandExecutor()
testCases := []TestCase{
{
name: "ZPOPMIN on non-existing key with/without count argument",
commands: []HTTPCommand{
{Command: "ZPOPMIN", Body: map[string]interface{}{"key": "NON_EXISTENT_KEY"}},
},
expected: []interface{}{[]interface{}{}},
},
{
name: "ZPOPMIN with wrong type of key with/without count argument",
commands: []HTTPCommand{
{Command: "SET", Body: map[string]interface{}{"key": "stringkey", "value": "string_value"}},
{Command: "ZPOPMIN", Body: map[string]interface{}{"key": "stringkey"}},
},
expected: []interface{}{"OK", "WRONGTYPE Operation against a key holding the wrong kind of value", float64(1)},
},
{
name: "ZPOPMIN on existing key (without count argument)",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "myzset", "values": [...]string{"1", "member1", "2", "member2", "3", "member3"}}},
{Command: "ZPOPMIN", Body: map[string]interface{}{"key": "myzset"}},
},
expected: []interface{}{float64(3), []interface{}{"member1", "1"}, float64(1)},
},
{
name: "ZPOPMIN with normal count argument",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "myzset", "values": [...]string{"1", "member1", "2", "member2", "3", "member3"}}},
{Command: "ZPOPMIN", Body: map[string]interface{}{"key": "myzset", "value": int64(2)}},
},
expected: []interface{}{float64(3), []interface{}{"member1", "1", "member2", "2"}, float64(1)},
},
{
name: "ZPOPMIN with count argument but multiple members have the same score",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "myzset", "values": [...]string{"1", "member1", "1", "member2", "1", "member3"}}},
{Command: "ZPOPMIN", Body: map[string]interface{}{"key": "myzset", "value": int64(2)}},
},
expected: []interface{}{float64(3), []interface{}{"member1", "1", "member2", "1"}, float64(1)},
},
{
name: "ZPOPMIN with negative count argument",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "myzset", "values": [...]string{"1", "member1", "2", "member2", "3", "member3"}}},
{Command: "ZPOPMIN", Body: map[string]interface{}{"key": "myzset", "value": int64(-1)}},
},
expected: []interface{}{float64(3), []interface{}{}, float64(1)},
},
{
name: "ZPOPMIN with invalid count argument",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "myzset", "values": [...]string{"1", "member1"}}},
{Command: "ZPOPMIN", Body: map[string]interface{}{"key": "myzset", "value": "INCORRECT_COUNT_ARGUMENT"}},
},
expected: []interface{}{float64(1), "ERR value is not an integer or out of range", float64(1)},
},
{
name: "ZPOPMIN with count argument greater than length of sorted set",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "myzset", "values": [...]string{"1", "member1", "2", "member2", "3", "member3"}}},
{Command: "ZPOPMIN", Body: map[string]interface{}{"key": "myzset", "value": int64(10)}},
},
expected: []interface{}{float64(3), []interface{}{"member1", "1", "member2", "2", "member3", "3"}, float64(1)},
},
{
name: "ZPOPMIN on empty sorted set",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "myzset", "values": [...]string{"1", "member1"}}},
{Command: "ZPOPMIN", Body: map[string]interface{}{"key": "myzset", "value": int64(1)}},
{Command: "ZPOPMIN", Body: map[string]interface{}{"key": "myzset"}},
},
expected: []interface{}{float64(1), []interface{}{"member1", "1"}, []interface{}{}, float64(1)},
},
{
name: "ZPOPMIN with floating-point scores",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "myzset", "values": [...]string{"1.5", "member1", "2.7", "member2", "3.8", "member3"}}},
{Command: "ZPOPMIN", Body: map[string]interface{}{"key": "myzset"}},
},
expected: []interface{}{float64(3), []interface{}{"member1", "1.5"}, float64(1)},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
exec.FireCommand(HTTPCommand{
Command: "DEL",
Body: map[string]interface{}{"key": "myzset"},
})
for i, cmd := range tc.commands {
result, _ := exec.FireCommand(cmd)

assert.DeepEqual(t, tc.expected[i], result)
}
})
}
}
Loading

0 comments on commit 045376e

Please sign in to comment.