Skip to content

Commit 39316ba

Browse files
committed
Add ZScan Method
1 parent 0027bd9 commit 39316ba

File tree

2 files changed

+60
-49
lines changed

2 files changed

+60
-49
lines changed

src/M6Web/Component/RedisMock/RedisMock.php

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,46 +1219,38 @@ public function zunionstore($destination, array $keys, array $options = array())
12191219
* @param array $options contain options of the command, with values (ex ['MATCH' => 'st*', 'COUNT' => 42] )
12201220
* @return $this|array|mixed
12211221
*/
1222-
public function zscan($key, $cursor = 0, array $options = [])
1222+
public function zscan($key, $cursor, $options = [])
12231223
{
1224-
$match = isset($options['MATCH']) ? $options['MATCH'] : '*';
1225-
$count = isset($options['COUNT']) ? $options['COUNT'] : 10;
1226-
$maximumValue = $cursor + $count -1;
1224+
$options = array_change_key_case($options, CASE_UPPER); // normalize to match Laravel/Symfony
1225+
$count = isset($options[ 'COUNT' ]) ? (int)$options[ 'COUNT' ] : 10;
1226+
$match = isset($options[ 'MATCH' ]) ? $options[ 'MATCH' ] : '*';
1227+
$pattern = sprintf('/^%s$/', str_replace(['*', '/'], ['.*', '\/'], $match));
1228+
1229+
$iterator = $cursor;
12271230

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

1232-
// Sorted set of all values in the storage.
1233-
$set = self::$dataValues[$this->storage][$key];
1234-
$maximumListElement = count($set);
1235+
$set = self::$dataValues[ $this->storage ][ $key ];
12351236

1236-
// Next cursor position
1237-
$nextCursorPosition = 0;
1238-
// Matched values.
1239-
$values = [];
1240-
// Pattern, for find matched values.
1241-
$pattern = sprintf('/^%s$/', str_replace(['*', '/'], ['.*', '\/'], $match));
1237+
if ($match !== '*') {
1238+
$set = array_filter($set, function($key) use ($pattern) {
1239+
return preg_match($pattern, $key);
1240+
}, ARRAY_FILTER_USE_KEY);
1241+
}
12421242

1243-
// Iterate over the sorted set starting from the given cursor position.
1244-
for($i = $cursor; $i <= $maximumValue; $i++)
1245-
{
1246-
if (isset($set[$i])){
1247-
$nextCursorPosition = $i >= $maximumListElement ? 0 : $i + 1;
1243+
$results = array_slice($set, $iterator, $count, true);
1244+
$iterator += count($results);
12481245

1249-
// Check if the score matches the pattern
1250-
$score = $set[$i][0];
1251-
if ('*' === $match || 1 === preg_match($pattern, $set[$i][1])){
1252-
$values[] = $set[$i][1];
1253-
}
12541246

1255-
} else {
1256-
// Out of the arrays values, return first element
1257-
$nextCursorPosition = 0;
1258-
}
1247+
if ($count <= count($results)) {
1248+
// there are more elements to scan
1249+
return $this->returnPipedInfo([$iterator, $results]);
1250+
} else {
1251+
// the end of the list has been reached
1252+
return $this->returnPipedInfo([0, $results]);
12591253
}
1260-
1261-
return $this->returnPipedInfo([$nextCursorPosition, $values]);
12621254
}
12631255

12641256
// Server

tests/units/RedisMock.php

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*/
1212
class RedisMock extends atoum
1313
{
14+
1415
public function testSetGetDelExists()
1516
{
1617
$redisMock = new Redis();
@@ -2077,44 +2078,62 @@ public function testSscanCommand()
20772078
public function testZscanCommand()
20782079
{
20792080
$redisMock = new Redis();
2080-
$redisMock->zadd('myZset', 1, 'a1');
2081-
$redisMock->zadd('myZset', ['b1' => 2, 'b2' => 3, 'b3' => 4, 'b4' => 5, 'b5' => 6, 'b6' => 7]);
2082-
$redisMock->zadd('myZset', ['c1' => 8, 'c2' => 9, 'c3' => 10]);
2083-
$redisMock->zadd('a/b', 11, 'c/d');
2081+
$redisMock->zadd('set1', 1, 'a:1');
2082+
$redisMock->zadd('set1', 2, 'b:1');
2083+
$redisMock->zadd('set1', 3, 'c:1');
2084+
$redisMock->zadd('set1', 4, 'd:1');
2085+
2086+
// Could be removed: ensure we have some noise of multiple sets
2087+
$redisMock->zadd('set2', 1, 'x:1');
2088+
$redisMock->zadd('set2', 2, 'y:1');
2089+
$redisMock->zadd('set2', 3, 'z:1');
20842090

20852091
// It must return no values, as the key is unknown.
20862092
$this->assert
2087-
->array($redisMock->zscan('unknown', 1, ['COUNT' => 2]))
2093+
->array($redisMock->zscan('unknown', 0, ['COUNT' => 10]))
20882094
->isEqualTo([0, []]);
20892095

2096+
2097+
// It must return all the values with score greater than or equal to 1.
20902098
$this->assert
2091-
->array($redisMock->zscan('a/b', 0, ['MATCH' => 'c/*']))
2092-
->isEqualTo([0, [0 => 'c/d']]);
2099+
->array($redisMock->zscan('set1', 0, ['MATCH' => '*', 'COUNT' => 10]))
2100+
->isEqualTo([0 => 0, 1 => ['a:1' => 1, 'b:1' => 2, 'c:1' => 3, 'd:1' => 4]]);
20932101

2094-
// It must return two values, start cursor after the first value of the set.
2102+
// It must return only the matched value
20952103
$this->assert
2096-
->array($redisMock->zscan('myZset', 1, ['COUNT' => 2]))
2097-
->isEqualTo([3, [0 => 'b1', 1 => 'b2']]);
2104+
->array($redisMock->zscan('set1', 0, ['MATCH' => 'c*', 'COUNT' => 10]))
2105+
->isEqualTo([0 => 0, 1 => ['c:1' => 3]]);
2106+
2107+
// It must return all of the values based on the match of *1
2108+
$this->assert
2109+
->array($redisMock->zscan('set1', 0, ['MATCH' => '*1', 'COUNT' => 10]))
2110+
->isEqualTo([0 => 0, 1 => ['a:1' => 1, 'b:1' => 2, 'c:1' => 3, 'd:1' => 4]]);
2111+
2112+
// It must return two values, starting cursor after the first value of the list.
20982113

2099-
// It must return all the values with score greater than or equal to 8.
2100-
// And the cursor is defined after the default count (10) => the match has not terminate all the set.
21012114
$this->assert
2102-
->array($redisMock->zscan('myZset', 0, ['MATCH' => '*', 'COUNT' => 10]))
2103-
->isEqualTo([10, [0 => 'a1', 1 => 'b1', 2 => 'b2', 3 => 'b3', 4 => 'b4', 5 => 'b5', 6 => 'b6', 7 => 'c1', 8 => 'c2', 9 => 'c3']]);
2115+
->array($redisMock->zscan('set1', 1, ['COUNT' => 2]))
2116+
->isEqualTo([3, ['b:1' => 2, 'c:1' => 3]]);
21042117

2105-
// Execute the match at the end of this set, the match not return an element (no one element match with the regex),
2106-
// And the set is terminate, return the cursor to the start (0)
2118+
// Ensure if our results are complete we return a zero cursor
21072119
$this->assert
2108-
->array($redisMock->zscan('myZset', 11, ['MATCH' => 'd*']))
2109-
->isEqualTo([0, []]);
2120+
->array($redisMock->zscan('set1', 3, ['COUNT' => 2]))
2121+
->isEqualTo([0, ['d:1' => 4]]);
21102122

2111-
$redisMock->expire('myZset', 1);
2123+
// It must return all the values with score greater than or equal to 3,
2124+
// starting cursor after the last value of the previous scan.
2125+
$this->assert
2126+
->array($redisMock->zscan('set1', 4, ['MATCH' => '*', 'COUNT' => 10]))
2127+
->isEqualTo([0 => 0, 1 => []]);
2128+
2129+
$redisMock->expire('set1', 1);
21122130
sleep(2);
21132131

21142132
// It must return no values, as the key is expired.
21152133
$this->assert
2116-
->array($redisMock->zscan('myZset', 1, ['COUNT' => 2]))
2134+
->array($redisMock->zscan('set1', 0, ['COUNT' => 2]))
21172135
->isEqualTo([0, []]);
2136+
21182137
}
21192138

21202139
public function testBitcountCommand()

0 commit comments

Comments
 (0)