Skip to content

Commit

Permalink
Merge pull request #107 from vemaeg/feature/sscan-implementation
Browse files Browse the repository at this point in the history
sscan implementation based on existing scan method
  • Loading branch information
omansour authored Dec 29, 2022
2 parents 2177985 + 6b74103 commit 04bf5e9
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Redis command | Description
**RPUSH** *key* *value* | Pushs values at the tail of a list
**RPOP** *key* | Pops values at the tail of a list
**SCAN** | Iterates the set of keys in the currently selected Redis database.
**SSCAN** | Iterates elements of Sets types.
**SET** *key* *value* | Sets the string value of a key
**SETEX** *key* *seconds* *value* | Sets the value and expiration of a key
**SETNX** *key* *value* | Sets key to hold value if key does not exist
Expand Down
48 changes: 48 additions & 0 deletions src/M6Web/Component/RedisMock/RedisMock.php
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,54 @@ public function sismember($key, $member)
return $this->returnPipedInfo(1);
}


/**
* Mock the `sscan` command
* @see https://redis.io/commands/sscan
* @param string $key
* @param int $cursor
* @param array $options contain options of the command, with values (ex ['MATCH' => 'st*', 'COUNT' => 42] )
* @return $this|array|mixed
*/
public function sscan($key, $cursor = 0, array $options = [])
{
$match = isset($options['MATCH']) ? $options['MATCH'] : '*';
$count = isset($options['COUNT']) ? $options['COUNT'] : 10;
$maximumValue = $cursor + $count -1;

if (!isset(self::$dataValues[$this->storage][$key]) || $this->deleteOnTtlExpired($key)) {
return $this->returnPipedInfo([0, []]);
}

// List of all keys in the storage (already ordered by index).
$set = self::$dataValues[$this->storage][$key];
$maximumListElement = count($set);

// Next cursor position
$nextCursorPosition = 0;
// Matched values.
$values = [];
// Pattern, for find matched values.
$pattern = sprintf('/^%s$/', str_replace(['*', '/'], ['.*', '\/'], $match));

for($i = $cursor; $i <= $maximumValue; $i++)
{
if (isset($set[$i])){
$nextCursorPosition = $i >= $maximumListElement ? 0 : $i + 1;

if ('*' === $match || 1 === preg_match($pattern, $set[$i])){
$values[] = $set[$i];
}

} else {
// Out of the arrays values, return first element
$nextCursorPosition = 0;
}
}

return $this->returnPipedInfo([$nextCursorPosition, $values]);
}

// Lists

public function llen($key)
Expand Down
43 changes: 43 additions & 0 deletions tests/units/RedisMock.php
Original file line number Diff line number Diff line change
Expand Up @@ -2031,6 +2031,49 @@ public function testScanCommand()
->isEqualTo([0, [0 => 'slash-key/with/slashes/1', 1 => 'slash-key/with/slashes/2']]);
}

public function testSscanCommand()
{
$redisMock = new Redis();
$redisMock->sadd('myKey', 'a1');
$redisMock->sadd('myKey', ['b1', 'b2', 'b3', 'b4', 'b5', 'b6']);
$redisMock->sadd('myKey', ['c1', 'c2', 'c3']);
$redisMock->sadd('a/b', 'c/d');

// It must return no values, as the key is unknown.
$this->assert
->array($redisMock->sscan('unknown', 1, ['COUNT' => 2]))
->isEqualTo([0, []]);

$this->assert
->array($redisMock->sscan('a/b', 0, ['MATCH' => 'c/*']))
->isEqualTo([0, [0 => 'c/d']]);

// It must return two values, start cursor after the first value of the list.
$this->assert
->array($redisMock->sscan('myKey', 1, ['COUNT' => 2]))
->isEqualTo([3, [0 => 'b1', 1 => 'b2']]);

// It must return all the values with match with the regex 'our' (2 keys).
// And the cursor is defined after the default count (10) => the match has not terminate all the list.
$this->assert
->array($redisMock->sscan('myKey', 0, ['MATCH' => 'c*']))
->isEqualTo([10, [0 => 'c1', 1 => 'c2', 2 => 'c3']]);

// Execute the match at the end of this list, the match not return an element (no one element match with the regex),
// And the list is terminate, return the cursor to the start (0)
$this->assert
->array($redisMock->sscan('myKey', 11, ['MATCH' => 'c*']))
->isEqualTo([0, []]);

$redisMock->expire('myKey', 1);
sleep(2);

// It must return no values, as the key is expired.
$this->assert
->array($redisMock->sscan('myKey', 1, ['COUNT' => 2]))
->isEqualTo([0, []]);
}

public function testBitcountCommand()
{
$redisMock = new Redis();
Expand Down

0 comments on commit 04bf5e9

Please sign in to comment.