Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ab55389

Browse files
SCAN and variants
This commit introduces support for the Redis SCAN, HSCAN, SSCAN, and ZSCAN commands. In the case of HSCAN, SSCAN, and ZSCAN, we take a key and iterator as required arguments, and for SCAN just an iterator. Matching the Redis commands, each variant can optionally take a pattern to match against and a count value which hints at Redis how many keys to return at a time. When scanning keys or members (especially with a large keyspace when searching for a pattern), Redis will sometimes return an empty result of keys/members. PHPRedis can be set up to abstract this from the caller by setting: $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); Which instructs PHPRedis to keep retrying the scan until members are returned OR the iteration completes (Redis returns to us a zero iterator). By default this option is set to Redis::SCAN_NORETRY, meaning that empty results are possible, requiring an explicit check for FALSE in the scanning loop, like so: ```php $it = NULL; while(($arr_keys = $redis->scan($it, "*pattern*"))!==FALSE) { print_r($arr_keys); } ```
1 parent 77afbe3 commit ab55389

File tree

7 files changed

+609
-48
lines changed

7 files changed

+609
-48
lines changed

README.markdown

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,15 @@ $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); // use built-in
268268
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY); // use igBinary serialize/unserialize
269269
270270
$redis->setOption(Redis::OPT_PREFIX, 'myAppName:'); // use custom prefix on all keys
271+
272+
/* Options for the SCAN family of commands, indicating whether to abstract
273+
empty results from the user. If set to SCAN_NORETRY (the default), phpredis
274+
will just issue one SCAN command at a time, sometimes returning an empty
275+
array of results. If set to SCAN_RETRY, phpredis will retry the scan command
276+
until keys come back OR Redis returns an iterator of zero
277+
*/
278+
$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_NORETRY);
279+
$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
271280
~~~
272281

273282

@@ -607,6 +616,7 @@ $redis->slowlog('len');
607616
* [expire, setTimeout, pexpire](#expire-settimeout-pexpire) - Set a key's time to live in seconds
608617
* [expireAt, pexpireAt](#expireat-pexpireat) - Set the expiration for a key as a UNIX timestamp
609618
* [keys, getKeys](#keys-getkeys) - Find all keys matching the given pattern
619+
* [scan](#scan) - Scan for keys in the keyspace (Redis >= 2.8.0)
610620
* [migrate](#migrate) - Atomically transfer a key from a Redis instance to another one
611621
* [move](#move) - Move a key to another database
612622
* [object](#object) - Inspect the internals of Redis objects
@@ -953,7 +963,29 @@ $allKeys = $redis->keys('*'); // all keys will match this.
953963
$keyWithUserPrefix = $redis->keys('user*');
954964
~~~
955965

966+
### scan
967+
-----
968+
_**Description**_: Scan the keyspace for keys
969+
970+
##### *Parameters*
971+
*LONG (reference)*: Iterator, initialized to NULL
972+
*STRING, Optional*: Pattern to match
973+
*LONG, Optional)*: Count of keys per iteration (only a suggestion to Redis)
956974

975+
##### *Return value*
976+
*Array, boolean*: This function will return an array of keys or FALSE if there are no more keys
977+
978+
##### *Example*
979+
~~~
980+
$it = NULL; /* Initialize our iterator to NULL */
981+
$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); /* retry when we get no keys back */
982+
while($arr_keys = $redis->scan($it)) {
983+
foreach($arr_keys as $str_key) {
984+
echo "Here is a key: $str_key\n";
985+
}
986+
echo "No more keys to scan!\n";
987+
}
988+
~~~
957989

958990
### object
959991
-----
@@ -1283,7 +1315,7 @@ $redis->migrate('backup', 6379, 'foo', 0, 3600);
12831315
* [hSet](#hset) - Set the string value of a hash field
12841316
* [hSetNx](#hsetnx) - Set the value of a hash field, only if the field does not exist
12851317
* [hVals](#hvals) - Get all the values in a hash
1286-
1318+
* [hScan](#hscan) - Scan a hash key for members
12871319
### hSet
12881320
-----
12891321
_**Description**_: Adds a value to the hash stored at key. If this value is already in the hash, `FALSE` is returned.
@@ -1542,7 +1574,28 @@ $redis->hSet('h', 'field2', 'value2');
15421574
$redis->hmGet('h', array('field1', 'field2')); /* returns array('field1' => 'value1', 'field2' => 'value2') */
15431575
~~~
15441576

1577+
### hScan
1578+
-----
1579+
_**Description**_: Scan a HASH value for members, with an optional pattern and count
1580+
##### *Parameters*
1581+
*key*: String
1582+
*iterator*: Long (reference)
1583+
*pattern*: Optional pattern to match against
1584+
*count*: How many keys to return in a go (only a sugestion to Redis)
1585+
##### *Return value*
1586+
*Array* An array of members that match our pattern
15451587

1588+
##### *Examples*
1589+
~~~
1590+
$it = NULL;
1591+
/* Don't ever return an empty array until we're done iterating */
1592+
$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
1593+
while($arr_keys = $redis->hscan('hash', $it)) {
1594+
foreach($arr_keys as $str_field => $str_value) {
1595+
echo "$str_field => $str_value\n"; /* Print the hash member and value */
1596+
}
1597+
}
1598+
~~~
15461599

15471600
## Lists
15481601

@@ -1981,6 +2034,7 @@ $redis->lSize('key1');/* 2 */
19812034
* [sRem, sRemove](#srem-sremove) - Remove one or more members from a set
19822035
* [sUnion](#sunion) - Add multiple sets
19832036
* [sUnionStore](#sunionstore) - Add multiple sets and store the resulting set in a key
2037+
* [sScan](#sscan) - Scan a set for members
19842038

19852039
### sAdd
19862040
-----
@@ -2380,6 +2434,41 @@ array(4) {
23802434
}
23812435
~~~
23822436

2437+
### sScan
2438+
-----
2439+
_**Description**_: Scan a set for members
2440+
2441+
##### *Parameters*
2442+
*Key*: The set to search
2443+
*iterator*: LONG (reference) to the iterator as we go
2444+
*pattern*: String, optional pattern to match against
2445+
*count*: How many members to return at a time (Redis might return a different amount)
2446+
2447+
##### *Retur value*
2448+
*Array, boolean*: PHPRedis will return an array of keys or FALSE when we're done iterating
2449+
2450+
##### *Example*
2451+
~~~
2452+
$it = NULL;
2453+
$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); /* don't return empty results until we're done */
2454+
while($arr_mems = $redis->sscan('set', $it, "*pattern*")) {
2455+
foreach($arr_mems as $str_mem) {
2456+
echo "Member: $str_mem\n";
2457+
}
2458+
}
2459+
2460+
$it = NULL;
2461+
$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_NORETRY); /* return after each iteration, even if empty */
2462+
while(($arr_mems = $redis->sscan('set', $it, "*pattern*"))!==FALSE) {
2463+
if(count($arr_mems) > 0) {
2464+
foreach($arr_mems as $str_mem) {
2465+
echo "Member found: $str_mem\n";
2466+
}
2467+
} else {
2468+
echo "No members in this iteration, iterator value: $it\n");
2469+
}
2470+
}
2471+
~~~
23832472

23842473
## Sorted sets
23852474

@@ -2397,6 +2486,7 @@ array(4) {
23972486
* [zRevRange](#zrevrange) - Return a range of members in a sorted set, by index, with scores ordered from high to low
23982487
* [zScore](#zscore) - Get the score associated with the given member in a sorted set
23992488
* [zUnion](#zunion) - Add multiple sorted sets and store the resulting sorted set in a new key
2489+
* [zScan](#zscan) - Scan a sorted set for members
24002490

24012491
### zAdd
24022492
-----
@@ -2736,6 +2826,30 @@ $redis->zUnion('ko2', array('k1', 'k2'), array(1, 1)); /* 4, 'ko2' => array('val
27362826
$redis->zUnion('ko3', array('k1', 'k2'), array(5, 1)); /* 4, 'ko3' => array('val0', 'val2', 'val3', 'val1') */
27372827
~~~
27382828

2829+
### zScan
2830+
-----
2831+
_**Description*_: Scan a sorted set for members, with optional pattern and count
2832+
2833+
##### *Parameters*
2834+
*key*: String, the set to scan
2835+
*iterator*: Long (reference), initialized to NULL
2836+
*pattern*: String (optional), the pattern to match
2837+
*count*: How many keys to return per iteration (Redis might return a different number)
2838+
2839+
##### *Return value*
2840+
*Array, boolean* PHPReids will return matching keys from Redis, or FALSE when iteration is complete
2841+
2842+
##### *Example*
2843+
~~~
2844+
$it = NULL;
2845+
$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
2846+
while($arr_matches = $redis->zscan('zset', $it, '*pattern*')) {
2847+
foreach($arr_matches as $str_mem => $f_score) {
2848+
echo "Key: $str_mem, Score: $f_score\n";
2849+
}
2850+
}
2851+
~~~
2852+
27392853
## Pub/sub
27402854

27412855
* [psubscribe](#psubscribe) - Subscribe to channels by pattern

common.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,30 @@ typedef enum _REDIS_REPLY_TYPE {
3737
TYPE_MULTIBULK = '*'
3838
} REDIS_REPLY_TYPE;
3939

40+
/* SCAN variants */
41+
typedef enum _REDIS_SCAN_TYPE {
42+
TYPE_SCAN,
43+
TYPE_SSCAN,
44+
TYPE_HSCAN,
45+
TYPE_ZSCAN
46+
} REDIS_SCAN_TYPE;
47+
4048
/* options */
4149
#define REDIS_OPT_SERIALIZER 1
4250
#define REDIS_OPT_PREFIX 2
4351
#define REDIS_OPT_READ_TIMEOUT 3
52+
#define REDIS_OPT_SCAN 4
4453

4554
/* serializers */
4655
#define REDIS_SERIALIZER_NONE 0
4756
#define REDIS_SERIALIZER_PHP 1
4857
#define REDIS_SERIALIZER_IGBINARY 2
4958

59+
/* SCAN options */
60+
61+
#define REDIS_SCAN_NORETRY 0
62+
#define REDIS_SCAN_RETRY 1
63+
5064
/* GETBIT/SETBIT offset range limits */
5165
#define BITOP_MIN_OFFSET 0
5266
#define BITOP_MAX_OFFSET 4294967295
@@ -57,6 +71,7 @@ typedef enum _REDIS_REPLY_TYPE {
5771
#define IF_MULTI_OR_PIPELINE() if(redis_sock->mode == MULTI || redis_sock->mode == PIPELINE)
5872
#define IF_PIPELINE() if(redis_sock->mode == PIPELINE)
5973
#define IF_NOT_MULTI() if(redis_sock->mode != MULTI)
74+
#define IF_NOT_ATOMIC() if(redis_sock->mode != ATOMIC)
6075
#define IF_ATOMIC() if(redis_sock->mode == ATOMIC)
6176
#define ELSE_IF_MULTI() else if(redis_sock->mode == MULTI) { \
6277
if(redis_response_enqueued(redis_sock TSRMLS_CC) == 1) {\
@@ -201,6 +216,8 @@ typedef struct {
201216
char *err;
202217
int err_len;
203218
zend_bool lazy_connect;
219+
220+
int scan;
204221
} RedisSock;
205222
/* }}} */
206223

library.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,54 @@ PHPAPI int redis_check_eof(RedisSock *redis_sock TSRMLS_DC)
100100
return 0;
101101
}
102102

103+
104+
PHPAPI int
105+
redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
106+
REDIS_SCAN_TYPE type, long *iter TSRMLS_DC)
107+
{
108+
REDIS_REPLY_TYPE reply_type;
109+
int reply_info;
110+
char *p_iter;
111+
112+
// Our response should have two multibulk replies
113+
if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC)<0
114+
|| reply_type != TYPE_MULTIBULK || reply_info != 2)
115+
{
116+
return -1;
117+
}
118+
119+
// The BULK response iterator
120+
if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC)<0
121+
|| reply_type != TYPE_BULK)
122+
{
123+
return -1;
124+
}
125+
126+
// Attempt to read the iterator
127+
if(!(p_iter = redis_sock_read_bulk_reply(redis_sock, reply_info TSRMLS_CC))) {
128+
return -1;
129+
}
130+
131+
// Push the iterator out to the caller
132+
*iter = atol(p_iter);
133+
efree(p_iter);
134+
135+
// Read our actual keys/members/etc differently depending on what kind of
136+
// scan command this is. They all come back in slightly different ways
137+
switch(type) {
138+
case TYPE_SCAN:
139+
return redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
140+
case TYPE_SSCAN:
141+
return redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
142+
case TYPE_ZSCAN:
143+
return redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
144+
case TYPE_HSCAN:
145+
return redis_sock_read_multibulk_reply_zipped_strings(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
146+
default:
147+
return -1;
148+
}
149+
}
150+
103151
PHPAPI zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
104152
char inbuf[1024];
105153
int numElems;
@@ -1064,6 +1112,8 @@ PHPAPI RedisSock* redis_sock_create(char *host, int host_len, unsigned short por
10641112
redis_sock->err = NULL;
10651113
redis_sock->err_len = 0;
10661114

1115+
redis_sock->scan = REDIS_SCAN_NORETRY;
1116+
10671117
return redis_sock;
10681118
}
10691119

library.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ PHPAPI int redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, Re
3535
PHPAPI int redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
3636
PHPAPI int redis_sock_read_multibulk_reply_zipped_strings(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
3737
PHPAPI int redis_sock_read_multibulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
38+
PHPAPI int redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_SCAN_TYPE type, long *iter TSRMLS_DC);
39+
3840
PHPAPI int redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz TSRMLS_DC);
3941
PHPAPI void redis_stream_close(RedisSock *redis_sock TSRMLS_DC);
4042
PHPAPI int redis_check_eof(RedisSock *redis_sock TSRMLS_DC);

php_redis.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,13 @@ PHP_METHOD(Redis, wait);
186186

187187
PHP_METHOD(Redis, client);
188188

189+
/* SCAN and friends */
190+
PHP_METHOD(Redis, scan);
191+
PHP_METHOD(Redis, hscan);
192+
PHP_METHOD(Redis, sscan);
193+
PHP_METHOD(Redis, zscan);
194+
195+
/* Reflection */
189196
PHP_METHOD(Redis, getHost);
190197
PHP_METHOD(Redis, getPort);
191198
PHP_METHOD(Redis, getDBNum);

0 commit comments

Comments
 (0)