Skip to content

Commit

Permalink
DiceDB#1117 ADDED Support for ZPOPMAX (DiceDB#1158)
Browse files Browse the repository at this point in the history
  • Loading branch information
bhima2001 authored Oct 25, 2024
1 parent f885717 commit ad14321
Show file tree
Hide file tree
Showing 10 changed files with 634 additions and 5 deletions.
148 changes: 148 additions & 0 deletions docs/src/content/docs/commands/ZPOPMAX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
title: ZPOPMAX
description: The `ZPOPMAX` command in DiceDB is used to remove and return the members with the highest scores from the sorted set data structure at the specified key. The second argument count is optional which specifies the number of elements that needs to be popped from the sorted set.
---

The `ZPOPMAX` command in DiceDB is used to remove and return the members with the highest scores from the sorted set data structure at the specified key. The second argument count is optional which specifies the number of elements that needs to be popped from the sorted set.

## Syntax

```
ZPOPMAX 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 from highest to lowest. | 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 highest scores are returned and removed.
- The returned array contains the members and their corresponding scores in the order of highest to lowest.

## 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 'zpopMAX' 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 highest score from a non-existent sorted set:

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

### Existing Key (without count argument)

Popping the member with the highest 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> ZPOPMAX myzset
1) 1 "member1"
```

### With Count Argument

Popping multiple members with the highest 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> ZPOPMAX 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> ZPOPMAX 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> ZPOPMAX 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> ZPOPMAX 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> ZPOPMAX 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> ZPOPMAX 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> ZPOPMAX stringkey
(error) WRONGTYPE Operation against a key holding the wrong kind of value
```
104 changes: 104 additions & 0 deletions integration_tests/commands/http/zpopmax_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package http

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestZPOPMAX(t *testing.T) {
exec := NewHTTPCommandExecutor()
testCases := []TestCase{
{
name: "ZPOPMAX on non-existing key with/without count argument",
commands: []HTTPCommand{
{Command: "ZPOPMAX", Body: map[string]interface{}{"key": "invalidTest"}},
},
expected: []interface{}{[]interface{}{}},
},
{
name: "ZPOPMAX with wrong type of key with/without count argument",
commands: []HTTPCommand{
{Command: "SET", Body: map[string]interface{}{"key": "sortedSet", "value": "testString"}},
{Command: "ZPOPMAX", Body: map[string]interface{}{"key": "sortedSet"}},
},
expected: []interface{}{"OK", "WRONGTYPE Operation against a key holding the wrong kind of value"},
},
{
name: "ZPOPMAX on existing key (without count argument)",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "sortedSet", "values": [...]string{"1", "member1", "2", "member2", "3", "member3"}}},
{Command: "ZPOPMAX", Body: map[string]interface{}{"key": "sortedSet"}},
{Command: "ZCOUNT", Body: map[string]interface{}{"key": "sortedSet", "values": [...]string{"1", "2.98"}}},
},
expected: []interface{}{float64(3), []interface{}{"member3", "3"}, float64(2)},
},
{
name: "ZPOPMAX with normal count argument",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "sortedSet", "values": [...]string{"1", "member1", "2", "member2", "3", "member3"}}},
{Command: "ZPOPMAX", Body: map[string]interface{}{"key": "sortedSet", "value": int64(2)}},
{Command: "ZCOUNT", Body: map[string]interface{}{"key": "sortedSet", "values": [...]string{"0.44", "2"}}},
},
expected: []interface{}{float64(3), []interface{}{"member3", "3", "member2", "2"}, float64(1)},
},
{
name: "ZPOPMAX with count argument but multiple members have the same score",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "sortedSet", "values": [...]string{"1", "member1", "1", "member2", "1", "member3"}}},
{Command: "ZPOPMAX", Body: map[string]interface{}{"key": "sortedSet", "value": int64(2)}},
{Command: "ZCOUNT", Body: map[string]interface{}{"key": "sortedSet", "values": [...]string{"1", "2"}}},
},
expected: []interface{}{float64(3), []interface{}{"member3", "1", "member2", "1"}, float64(1)},
},
{
name: "ZPOPMAX with negative count argument",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "sortedSet", "values": [...]string{"1", "member1", "2", "member2", "3", "member3"}}},
{Command: "ZPOPMAX", Body: map[string]interface{}{"key": "sortedSet", "value": int64(-1)}},
{Command: "ZCOUNT", Body: map[string]interface{}{"key": "sortedSet", "values": [...]string{"1", "1000"}}},
},
expected: []interface{}{float64(3), []interface{}{}, float64(3)},
},
{
name: "ZPOPMAX with invalid count argument",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "sortedSet", "values": [...]string{"1", "member1"}}},
{Command: "ZPOPMAX", Body: map[string]interface{}{"key": "sortedSet", "value": "INCORRECT_COUNT_ARGUMENT"}},
{Command: "ZCOUNT", Body: map[string]interface{}{"key": "sortedSet", "values": [...]string{"1", "2"}}},
},
expected: []interface{}{float64(1), "ERR value is out of range, must be positive", float64(1)},
},
{
name: "ZPOPMAX with count argument greater than length of sorted set",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "sortedSet", "values": [...]string{"1", "member1", "2", "member2", "3", "member3"}}},
{Command: "ZPOPMAX", Body: map[string]interface{}{"key": "sortedSet", "value": int64(10)}},
{Command: "ZCOUNT", Body: map[string]interface{}{"key": "sortedSet", "values": [...]string{"1", "2"}}},
},
expected: []interface{}{float64(3), []interface{}{"member3", "3", "member2", "2", "member1", "1"}, float64(0)},
},
{
name: "ZPOPMAX with floating-point scores",
commands: []HTTPCommand{
{Command: "ZADD", Body: map[string]interface{}{"key": "sortedSet", "values": [...]string{"1.5", "member1", "2.7", "member2", "3.8", "member3"}}},
{Command: "ZPOPMAX", Body: map[string]interface{}{"key": "sortedSet"}},
{Command: "ZCOUNT", Body: map[string]interface{}{"key": "sortedSet", "values": [...]string{"1.3", "3.6"}}},
},
expected: []interface{}{float64(3), []interface{}{"member3", "3.8"}, float64(2)},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for i, cmd := range tc.commands {
result, _ := exec.FireCommand(cmd)
assert.Equal(t, tc.expected[i], result)
}
exec.FireCommand(HTTPCommand{
Command: "DEL",
Body: map[string]interface{}{"key": "sortedSet"},
})
})
}
}
75 changes: 75 additions & 0 deletions integration_tests/commands/resp/zpopmax_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package resp

import (
"testing"

"github.com/stretchr/testify/assert"
)

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

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

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for i, cmd := range tc.commands {
result := FireCommand(conn, cmd)
assert.Equal(t, tc.expected[i], result)
}
FireCommand(conn, "DEL sortedSet")
})
}
}
Loading

0 comments on commit ad14321

Please sign in to comment.