Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/phpredis/phpredis.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorvostok4 <vostok4@gmail.com>2014-04-09 13:14:45 +0400
committervostok4 <vostok4@gmail.com>2014-04-09 13:14:45 +0400
commit9c12c40a66cbd76f5efa053b1718fa936320400b (patch)
tree1dc06d47d219fde0b27b5066dd823afd6f349cf5
parent978fbcf6fc355fcd344feb76ab4b7d6b77e9f7c1 (diff)
Merge nicolasff:b9a16b5ad5 in, fixing for Win32
Now we should be up to master with upstream for an easier merge.
-rw-r--r--CREDITS1
-rw-r--r--README.markdown177
-rw-r--r--common.h28
-rw-r--r--library.c54
-rw-r--r--library.h2
-rw-r--r--package.xml92
-rw-r--r--php_redis.h14
-rw-r--r--redis.c609
-rw-r--r--redis_array.c72
-rw-r--r--redis_array.h1
-rw-r--r--redis_array_impl.c40
-rw-r--r--redis_array_impl.h2
-rw-r--r--rpm/php-redis.spec2
-rw-r--r--tests/TestRedis.php344
14 files changed, 1352 insertions, 86 deletions
diff --git a/CREDITS b/CREDITS
index c501060a..53010079 100644
--- a/CREDITS
+++ b/CREDITS
@@ -2,3 +2,4 @@ Redis client extension for PHP
Alfonso Jimenez (yo@alfonsojimenez.com)
Nasreddine Bouafif (n.bouafif@owlient.eu)
Nicolas Favre-Felix (n.favre-felix@owlient.eu)
+Michael Grunder (michael.grunder@gmail.com) \ No newline at end of file
diff --git a/README.markdown b/README.markdown
index 7d5d2dc8..91d09f19 100644
--- a/README.markdown
+++ b/README.markdown
@@ -3,7 +3,7 @@
The phpredis extension provides an API for communicating with the [Redis](http://redis.io/) key-value store. It is released under the [PHP License, version 3.01](http://www.php.net/license/3_01.txt).
This code has been developed and maintained by Owlient from November 2009 to March 2011.
-You can send comments, patches, questions [here on github](https://github.com/nicolasff/phpredis/issues) or to n.favrefelix@gmail.com ([@yowgi](http://twitter.com/yowgi)).
+You can send comments, patches, questions [here on github](https://github.com/nicolasff/phpredis/issues), to n.favrefelix@gmail.com ([@yowgi](http://twitter.com/yowgi)), or to michael.grunder@gmail.com ([@grumi78](http://twitter.com/grumi78)).
# Table of contents
@@ -69,6 +69,10 @@ Taken from [Compiling phpredis on Zend Server CE/OSX ](http://www.tumblr.com/tag
See also: [Install Redis & PHP Extension PHPRedis with Macports](http://www.lecloud.net/post/3378834922/install-redis-php-extension-phpredis-with-macports).
+You can install install it using Homebrew:
+
+- [Get homebrew-php](https://github.com/josegonzalez/homebrew-php)
+- `brew install php55-redis` (or php53-redis, php54-redis)
## PHP Session handler
@@ -268,6 +272,15 @@ $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); // use built-in
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY); // use igBinary serialize/unserialize
$redis->setOption(Redis::OPT_PREFIX, 'myAppName:'); // use custom prefix on all keys
+
+/* Options for the SCAN family of commands, indicating whether to abstract
+ empty results from the user. If set to SCAN_NORETRY (the default), phpredis
+ will just issue one SCAN command at a time, sometimes returning an empty
+ array of results. If set to SCAN_RETRY, phpredis will retry the scan command
+ until keys come back OR Redis returns an iterator of zero
+*/
+$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_NORETRY);
+$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
~~~
@@ -607,6 +620,7 @@ $redis->slowlog('len');
* [expire, setTimeout, pexpire](#expire-settimeout-pexpire) - Set a key's time to live in seconds
* [expireAt, pexpireAt](#expireat-pexpireat) - Set the expiration for a key as a UNIX timestamp
* [keys, getKeys](#keys-getkeys) - Find all keys matching the given pattern
+* [scan](#scan) - Scan for keys in the keyspace (Redis >= 2.8.0)
* [migrate](#migrate) - Atomically transfer a key from a Redis instance to another one
* [move](#move) - Move a key to another database
* [object](#object) - Inspect the internals of Redis objects
@@ -658,10 +672,10 @@ $redis->set('key', 'value');
$redis->set('key','value', 10);
// Will set the key, if it doesn't exist, with a ttl of 10 seconds
-$redis->set('key', 'value', Array('nx', 'ex'=>10);
+$redis->set('key', 'value', Array('nx', 'ex'=>10));
// Will set a key, if it does exist, with a ttl of 1000 miliseconds
-$redis->set('key', 'value', Array('xx', 'px'=>1000);
+$redis->set('key', 'value', Array('xx', 'px'=>1000));
~~~
@@ -780,7 +794,7 @@ $redis->incrByFloat('key1', 1.5); /* key1 didn't exist, so it will now be 1.5 */
$redis->incrByFloat('key1', 1.5); /* 3 */
$redis->incrByFloat('key1', -1.5); /* 1.5 */
-$redis->incrByFloat('key1', 2.5); /* 3.5 */
+$redis->incrByFloat('key1', 2.5); /* 4 */
~~~
### decr, decrBy
@@ -953,7 +967,29 @@ $allKeys = $redis->keys('*'); // all keys will match this.
$keyWithUserPrefix = $redis->keys('user*');
~~~
+### scan
+-----
+_**Description**_: Scan the keyspace for keys
+
+##### *Parameters*
+*LONG (reference)*: Iterator, initialized to NULL
+*STRING, Optional*: Pattern to match
+*LONG, Optional*: Count of keys per iteration (only a suggestion to Redis)
+##### *Return value*
+*Array, boolean*: This function will return an array of keys or FALSE if there are no more keys
+
+##### *Example*
+~~~
+$it = NULL; /* Initialize our iterator to NULL */
+$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); /* retry when we get no keys back */
+while($arr_keys = $redis->scan($it)) {
+ foreach($arr_keys as $str_key) {
+ echo "Here is a key: $str_key\n";
+ }
+ echo "No more keys to scan!\n";
+}
+~~~
### object
-----
@@ -1261,9 +1297,13 @@ _**Description**_: Migrates a key to a different Redis instance.
*key* string. The key to migrate.
*destination-db* integer. The target DB.
*timeout* integer. The maximum amount of time given to this transfer.
+*copy* boolean, optional. Should we send the COPY flag to redis
+*replace* boolean, optional. Should we send the REPLACE flag to redis
##### *Examples*
~~~
$redis->migrate('backup', 6379, 'foo', 0, 3600);
+$redis->migrate('backup', 6379, 'foo', 0, 3600, true, true); /* copy and replace */
+$redis->migrate('backup', 6379, 'foo', 0, 3600, false, true); /* just REPLACE flag */
~~~
@@ -1283,6 +1323,7 @@ $redis->migrate('backup', 6379, 'foo', 0, 3600);
* [hSet](#hset) - Set the string value of a hash field
* [hSetNx](#hsetnx) - Set the value of a hash field, only if the field does not exist
* [hVals](#hvals) - Get all the values in a hash
+* [hScan](#hscan) - Scan a hash key for members
### hSet
-----
@@ -1542,7 +1583,28 @@ $redis->hSet('h', 'field2', 'value2');
$redis->hmGet('h', array('field1', 'field2')); /* returns array('field1' => 'value1', 'field2' => 'value2') */
~~~
+### hScan
+-----
+_**Description**_: Scan a HASH value for members, with an optional pattern and count
+##### *Parameters*
+*key*: String
+*iterator*: Long (reference)
+*pattern*: Optional pattern to match against
+*count*: How many keys to return in a go (only a sugestion to Redis)
+##### *Return value*
+*Array* An array of members that match our pattern
+##### *Examples*
+~~~
+$it = NULL;
+/* Don't ever return an empty array until we're done iterating */
+$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
+while($arr_keys = $redis->hscan('hash', $it)) {
+ foreach($arr_keys as $str_field => $str_value) {
+ echo "$str_field => $str_value\n"; /* Print the hash member and value */
+ }
+}
+~~~
## Lists
@@ -1981,6 +2043,7 @@ $redis->lSize('key1');/* 2 */
* [sRem, sRemove](#srem-sremove) - Remove one or more members from a set
* [sUnion](#sunion) - Add multiple sets
* [sUnionStore](#sunionstore) - Add multiple sets and store the resulting set in a key
+* [sScan](#sscan) - Scan a set for members
### sAdd
-----
@@ -2380,6 +2443,41 @@ array(4) {
}
~~~
+### sScan
+-----
+_**Description**_: Scan a set for members
+
+##### *Parameters*
+*Key*: The set to search
+*iterator*: LONG (reference) to the iterator as we go
+*pattern*: String, optional pattern to match against
+*count*: How many members to return at a time (Redis might return a different amount)
+
+##### *Return value*
+*Array, boolean*: PHPRedis will return an array of keys or FALSE when we're done iterating
+
+##### *Example*
+~~~
+$it = NULL;
+$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); /* don't return empty results until we're done */
+while($arr_mems = $redis->sscan('set', $it, "*pattern*")) {
+ foreach($arr_mems as $str_mem) {
+ echo "Member: $str_mem\n";
+ }
+}
+
+$it = NULL;
+$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_NORETRY); /* return after each iteration, even if empty */
+while(($arr_mems = $redis->sscan('set', $it, "*pattern*"))!==FALSE) {
+ if(count($arr_mems) > 0) {
+ foreach($arr_mems as $str_mem) {
+ echo "Member found: $str_mem\n";
+ }
+ } else {
+ echo "No members in this iteration, iterator value: $it\n";
+ }
+}
+~~~
## Sorted sets
@@ -2397,6 +2495,7 @@ array(4) {
* [zRevRange](#zrevrange) - Return a range of members in a sorted set, by index, with scores ordered from high to low
* [zScore](#zscore) - Get the score associated with the given member in a sorted set
* [zUnion](#zunion) - Add multiple sorted sets and store the resulting sorted set in a new key
+* [zScan](#zscan) - Scan a sorted set for members
### zAdd
-----
@@ -2736,11 +2835,36 @@ $redis->zUnion('ko2', array('k1', 'k2'), array(1, 1)); /* 4, 'ko2' => array('val
$redis->zUnion('ko3', array('k1', 'k2'), array(5, 1)); /* 4, 'ko3' => array('val0', 'val2', 'val3', 'val1') */
~~~
+### zScan
+-----
+_**Description**_: Scan a sorted set for members, with optional pattern and count
+
+##### *Parameters*
+*key*: String, the set to scan
+*iterator*: Long (reference), initialized to NULL
+*pattern*: String (optional), the pattern to match
+*count*: How many keys to return per iteration (Redis might return a different number)
+
+##### *Return value*
+*Array, boolean* PHPRedis will return matching keys from Redis, or FALSE when iteration is complete
+
+##### *Example*
+~~~
+$it = NULL;
+$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
+while($arr_matches = $redis->zscan('zset', $it, '*pattern*')) {
+ foreach($arr_matches as $str_mem => $f_score) {
+ echo "Key: $str_mem, Score: $f_score\n";
+ }
+}
+~~~
+
## Pub/sub
* [psubscribe](#psubscribe) - Subscribe to channels by pattern
* [publish](#publish) - Post a message to a channel
* [subscribe](#subscribe) - Subscribe to channels
+* [pubsub](#pubsub) - Introspection into the pub/sub subsystem
### psubscribe
-----
@@ -2801,6 +2925,26 @@ function f($redis, $chan, $msg) {
$redis->subscribe(array('chan-1', 'chan-2', 'chan-3'), 'f'); // subscribe to 3 chans
~~~
+### pubsub
+-----
+_**Description**_: A command allowing you to get information on the Redis pub/sub system.
+
+##### *Parameters*
+*keyword*: String, which can be: "channels", "numsub", or "numpat"
+*argument*: Optional, variant. For the "channels" subcommand, you can pass a string pattern. For "numsub" an array of channel names.
+
+##### *Return value*
+*CHANNELS*: Returns an array where the members are the matching channels.
+*NUMSUB*: Returns a key/value array where the keys are channel names and values are their counts.
+*NUMPAT*: Integer return containing the number active pattern subscriptions
+
+##### *Example*
+~~~
+$redis->pubsub("channels"); /*All channels */
+$redis->pubsub("channels", "*pattern*"); /* Just channels matching your pattern */
+$redis->pubsub("numsub", Array("chan1", "chan2")); /*Get subscriber counts for 'chan1' and 'chan2'*/
+$redsi->pubsub("numpat"); /* Get the number of pattern subscribers */
+```
## Transactions
@@ -2867,6 +3011,7 @@ $ret = FALSE if x has been modified between the call to WATCH and the call to EX
* [clearLastError](#) - Clear the last error message
* [_prefix](#) - A utility method to prefix the value with the prefix setting for phpredis
* [_unserialize](#) - A utility method to unserialize data with whatever serializer is set up
+* [_serialize](#) - A utility method to serialize data with whatever serializer is set up
### eval
-----
@@ -3016,6 +3161,28 @@ $redis->setOption(Redis::OPT_PREFIX, 'my-prefix:');
$redis->_prefix('my-value'); // Will return 'my-prefix:my-value'
~~~
+### _serialize
+-----
+_**Description**_: A utility method to serialize values manually.
+
+This method allows you to serialize a value with whatever serializer is configured, manually.
+This can be useful for serialization/unserialization of data going in and out of EVAL commands
+as phpredis can't automatically do this itself. Note that if no serializer is set, phpredis
+will change Array values to 'Array', and Objects to 'Object'.
+
+##### *Parameters*
+*value*: Mixed. The value to be serialized
+
+##### *Examples*
+~~~
+$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE);
+$redis->_serialize("foo"); // returns "foo"
+$redis->_serialize(Array()); // Returns "Array"
+$redis->_serialize(new stdClass()); // Returns "Object"
+
+$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
+$redis->_serialize("foo"); // Returns 's:3:"foo";'
+
### _unserialize
-----
_**Description**_: A utility method to unserialize data with whatever serializer is set up.
@@ -3080,7 +3247,7 @@ None
### GetTimeout
-----
-_**Description**_: Get the (write) timeout in use for phpreids
+_**Description**_: Get the (write) timeout in use for phpredis
##### *Parameters*
None
diff --git a/common.h b/common.h
index 63b1d9ee..a0623fbd 100644
--- a/common.h
+++ b/common.h
@@ -37,22 +37,48 @@ typedef enum _REDIS_REPLY_TYPE {
TYPE_MULTIBULK = '*'
} REDIS_REPLY_TYPE;
+/* SCAN variants */
+typedef enum _REDIS_SCAN_TYPE {
+ TYPE_SCAN,
+ TYPE_SSCAN,
+ TYPE_HSCAN,
+ TYPE_ZSCAN
+} REDIS_SCAN_TYPE;
+
+/* PUBSUB subcommands */
+typedef enum _PUBSUB_TYPE {
+ PUBSUB_CHANNELS,
+ PUBSUB_NUMSUB,
+ PUBSUB_NUMPAT
+} PUBSUB_TYPE;
+
/* options */
#define REDIS_OPT_SERIALIZER 1
#define REDIS_OPT_PREFIX 2
#define REDIS_OPT_READ_TIMEOUT 3
+#define REDIS_OPT_SCAN 4
/* serializers */
#define REDIS_SERIALIZER_NONE 0
#define REDIS_SERIALIZER_PHP 1
#define REDIS_SERIALIZER_IGBINARY 2
+/* SCAN options */
+
+#define REDIS_SCAN_NORETRY 0
+#define REDIS_SCAN_RETRY 1
+
+/* GETBIT/SETBIT offset range limits */
+#define BITOP_MIN_OFFSET 0
+#define BITOP_MAX_OFFSET 4294967295
+
#define IF_MULTI() if(redis_sock->mode == MULTI)
#define IF_MULTI_OR_ATOMIC() if(redis_sock->mode == MULTI || redis_sock->mode == ATOMIC)\
#define IF_MULTI_OR_PIPELINE() if(redis_sock->mode == MULTI || redis_sock->mode == PIPELINE)
#define IF_PIPELINE() if(redis_sock->mode == PIPELINE)
#define IF_NOT_MULTI() if(redis_sock->mode != MULTI)
+#define IF_NOT_ATOMIC() if(redis_sock->mode != ATOMIC)
#define IF_ATOMIC() if(redis_sock->mode == ATOMIC)
#define ELSE_IF_MULTI() else if(redis_sock->mode == MULTI) { \
if(redis_response_enqueued(redis_sock TSRMLS_CC) == 1) {\
@@ -197,6 +223,8 @@ typedef struct {
char *err;
int err_len;
zend_bool lazy_connect;
+
+ int scan;
} RedisSock;
/* }}} */
diff --git a/library.c b/library.c
index 4fd523de..c368d3a3 100644
--- a/library.c
+++ b/library.c
@@ -106,6 +106,54 @@ PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock TSRMLS_DC)
return 0;
}
+
+PHP_REDIS_API int
+redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
+ REDIS_SCAN_TYPE type, long *iter)
+{
+ REDIS_REPLY_TYPE reply_type;
+ int reply_info;
+ char *p_iter;
+
+ /* Our response should have two multibulk replies */
+ if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC)<0
+ || reply_type != TYPE_MULTIBULK || reply_info != 2)
+ {
+ return -1;
+ }
+
+ /* The BULK response iterator */
+ if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC)<0
+ || reply_type != TYPE_BULK)
+ {
+ return -1;
+ }
+
+ /* Attempt to read the iterator */
+ if(!(p_iter = redis_sock_read_bulk_reply(redis_sock, reply_info TSRMLS_CC))) {
+ return -1;
+ }
+
+ /* Push the iterator out to the caller */
+ *iter = atol(p_iter);
+ efree(p_iter);
+
+ /* Read our actual keys/members/etc differently depending on what kind of
+ scan command this is. They all come back in slightly different ways */
+ switch(type) {
+ case TYPE_SCAN:
+ return redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
+ case TYPE_SSCAN:
+ return redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
+ case TYPE_ZSCAN:
+ return redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
+ case TYPE_HSCAN:
+ return redis_sock_read_multibulk_reply_zipped_strings(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
+ default:
+ return -1;
+ }
+}
+
PHP_REDIS_API zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
char inbuf[1024];
int numElems;
@@ -1074,6 +1122,8 @@ PHP_REDIS_API RedisSock* redis_sock_create(char *host, int host_len, unsigned sh
redis_sock->err = NULL;
redis_sock->err_len = 0;
+ redis_sock->scan = REDIS_SCAN_NORETRY;
+
return redis_sock;
}
@@ -1498,8 +1548,10 @@ redis_serialize(RedisSock *redis_sock, zval *z, char **val, int *val_len TSRMLS_
#endif
smart_str sstr = {0};
zval *z_copy;
+#ifdef HAVE_REDIS_IGBINARY
size_t sz;
uint8_t *val8;
+#endif
switch(redis_sock->serializer) {
case REDIS_SERIALIZER_NONE:
@@ -1834,7 +1886,7 @@ redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zv
default:
/* Protocol error */
zend_throw_exception_ex(redis_exception_ce, 0 TSRMLS_CC, "protocol error, got '%c' as reply-type byte\n", reply_type);
- break;
+ return FAILURE;
}
IF_MULTI_OR_PIPELINE() {
diff --git a/library.h b/library.h
index b749cf63..4813b126 100644
--- a/library.h
+++ b/library.h
@@ -35,6 +35,8 @@ PHP_REDIS_API int redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAMET
PHP_REDIS_API int redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API int redis_sock_read_multibulk_reply_zipped_strings(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API int redis_sock_read_multibulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
+PHP_REDIS_API int redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_SCAN_TYPE type, long *iter);
+
PHP_REDIS_API int redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz TSRMLS_DC);
PHP_REDIS_API void redis_stream_close(RedisSock *redis_sock TSRMLS_DC);
PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock TSRMLS_DC);
diff --git a/package.xml b/package.xml
index dd97ad77..f3b82d4f 100644
--- a/package.xml
+++ b/package.xml
@@ -21,10 +21,10 @@ http://pear.php.net/dtd/package-2.0.xsd">
<email>michael.grunder@gmail.com</email>
<active>yes</active>
</lead>
- <date>2013-09-01</date>
+ <date>2014-03-15</date>
<version>
- <release>2.2.4</release>
- <api>2.2.4</api>
+ <release>2.2.5</release>
+ <api>2.2.5</api>
</version>
<stability>
<release>stable</release>
@@ -32,7 +32,25 @@ http://pear.php.net/dtd/package-2.0.xsd">
</stability>
<license uri="http://www.php.net/license">PHP</license>
<notes>
- First public release
+ phpredis 2.2.5
+
+ This is a minor release with several bug fixes as well as additions to support
+ new commands that have been introduced to Redis since our last release.
+
+ A special thanks to everyone who helps the project by commenting on issues and
+ submitting pull requests! :)
+
+ [NEW] Support for the BITPOS command
+ [NEW] Connection timeout option for RedisArray (@MikeToString)
+ [NEW] A _serialize method, to complement our existing _unserialize method
+ [NEW] Support for the PUBSUB command
+ [NEW] Support for SCAN, SSCAN, HSCAN, and ZSCAN
+ [NEW] Support for the WAIT command
+
+ [FIX] Handle the COPY and REPLACE arguments for the MIGRATE command
+
+ [DOC] Fix syntax error in documentation for the SET command (@mithunsatheesh)
+ [DOC] Homebrew documentation instructions (@mathias)
</notes>
<contents>
<dir name="/">
@@ -53,6 +71,13 @@ http://pear.php.net/dtd/package-2.0.xsd">
<file role='src' name='redis.c'/>
<file role='src' name='redis_session.c'/>
<file role='src' name='redis_session.h'/>
+ <dir name='tests'>
+ <file role='test' name='array-tests.php' />
+ <file role='test' name='memory.php' />
+ <file role='test' name='mkring.sh' />
+ <file role='test' name='test.php' />
+ <file role='test' name='TestRedis.php' />
+ </dir> <!-- tests -->
</dir> <!-- / -->
</contents>
<dependencies>
@@ -72,10 +97,67 @@ http://pear.php.net/dtd/package-2.0.xsd">
<changelog>
<release>
<stability><release>stable</release><api>stable</api></stability>
+ <version><release>2.2.5</release><api>2.2.5</api></version>
+ <date>2014-03-15</date>
+ <notes>
+ phpredis 2.2.5
+
+ This is a minor release with several bug fixes as well as additions to support
+ new commands that have been introduced to Redis since our last release.
+
+ A special thanks to everyone who helps the project by commenting on issues and
+ submitting pull requests! :)
+
+ [NEW] Support for the BITPOS command
+ [NEW] Connection timeout option for RedisArray (@MikeToString)
+ [NEW] A _serialize method, to complement our existing _unserialize method
+ [NEW] Support for the PUBSUB command
+ [NEW] Support for SCAN, SSCAN, HSCAN, and ZSCAN
+ [NEW] Support for the WAIT command
+
+ [FIX] Handle the COPY and REPLACE arguments for the MIGRATE command
+
+ [DOC] Fix syntax error in documentation for the SET command (@mithunsatheesh)
+ [DOC] Homebrew documentation instructions (@mathias)
+
+ </notes>
+ </release>
+ <release>
+ <stability><release>stable</release><api>stable</api></stability>
<version><release>2.2.4</release><api>2.2.4</api></version>
<date>2013-09-01</date>
<notes>
- See GitHub for release notes
+ **
+ ** Features / Improvements
+ **
+
+ * Randomized reconnect delay for RedisArray @mobli
+ This feature adds an optional parameter when constructing a RedisArray object
+ such that a random delay will be introduced if reconnections are made,
+ mitigating any &apos;thundering herd&apos; type problems.
+
+ * Lazy connections to RedisArray servers @mobli
+ By default, RedisArray will attempt to connect to each server you pass in
+ the ring on construction. This feature lets you specify that you would
+ rather have RedisArray only attempt a connection when it needs to get data
+ from a particular node (throughput/performance improvement).
+
+ * Allow LONG and STRING keys in MGET/MSET
+ * Extended SET options for Redis &gt;= 2.6.12
+ * Persistent connections and UNIX SOCKET support for RedisArray
+ * Allow aggregates for ZUNION/ZINTER without weights @mheijkoop
+ * Support for SLOWLOG command
+ * Reworked MGET algorithm to run in linear time regardless of key count.
+ * Reworked ZINTERSTORE/ZUNIONSTORE algorithm to run in linear time
+
+ **
+ ** Bug fixes
+ **
+
+ * C99 Compliance (or rather lack thereof) fix @mobli
+ * Added ZEND_ACC_CTOR and ZEND_ACC_DTOR @euskadi31
+ * Stop throwing and clearing an exception on connect failure @matmoi
+ * Fix a false positive unit test failure having to do with TTL returns
</notes>
</release>
<release>
diff --git a/php_redis.h b/php_redis.h
index 905c7b3b..ccc2e724 100644
--- a/php_redis.h
+++ b/php_redis.h
@@ -14,6 +14,7 @@
+----------------------------------------------------------------------+
| Original author: Alfonso Jimenez <yo@alfonsojimenez.com> |
| Maintainer: Nicolas Favre-Felix <n.favre-felix@owlient.eu> |
+ | Maintainer: Michael Grunder <michael.grunder@gmail.com> |
| Maintainer: Nasreddine Bouafif <n.bouafif@owlient.eu> |
+----------------------------------------------------------------------+
*/
@@ -128,6 +129,7 @@ PHP_METHOD(Redis, slaveof);
PHP_METHOD(Redis, object);
PHP_METHOD(Redis, bitop);
PHP_METHOD(Redis, bitcount);
+PHP_METHOD(Redis, bitpos);
PHP_METHOD(Redis, eval);
PHP_METHOD(Redis, evalsha);
@@ -141,6 +143,7 @@ PHP_METHOD(Redis, time);
PHP_METHOD(Redis, getLastError);
PHP_METHOD(Redis, clearLastError);
PHP_METHOD(Redis, _prefix);
+PHP_METHOD(Redis, _serialize);
PHP_METHOD(Redis, _unserialize);
PHP_METHOD(Redis, mset);
@@ -181,9 +184,18 @@ PHP_METHOD(Redis, setOption);
PHP_METHOD(Redis, config);
PHP_METHOD(Redis, slowlog);
+PHP_METHOD(Redis, wait);
+PHP_METHOD(Redis, pubsub);
PHP_METHOD(Redis, client);
+/* SCAN and friends */
+PHP_METHOD(Redis, scan);
+PHP_METHOD(Redis, hscan);
+PHP_METHOD(Redis, sscan);
+PHP_METHOD(Redis, zscan);
+
+/* Reflection */
PHP_METHOD(Redis, getHost);
PHP_METHOD(Redis, getPort);
PHP_METHOD(Redis, getDBNum);
@@ -257,7 +269,7 @@ extern zend_module_entry redis_module_entry;
#define phpext_redis_ptr redis_module_ptr
-#define PHP_REDIS_VERSION "2.2.4"
+#define PHP_REDIS_VERSION "2.2.5"
#endif
diff --git a/redis.c b/redis.c
index f869d001..b565069d 100644
--- a/redis.c
+++ b/redis.c
@@ -69,6 +69,25 @@ PHP_INI_BEGIN()
PHP_INI_ENTRY("redis.arrays.autorehash", "", PHP_INI_ALL, NULL)
PHP_INI_END()
+/**
+ * Argument info for the SCAN proper
+ */
+ZEND_BEGIN_ARG_INFO_EX(arginfo_scan, 0, 0, 1)
+ ZEND_ARG_INFO(1, i_iterator)
+ ZEND_ARG_INFO(0, str_pattern)
+ ZEND_ARG_INFO(0, i_count)
+ZEND_END_ARG_INFO();
+
+/**
+ * Argument info for key scanning
+ */
+ZEND_BEGIN_ARG_INFO_EX(arginfo_kscan, 0, 0, 2)
+ ZEND_ARG_INFO(0, str_key)
+ ZEND_ARG_INFO(1, i_iterator)
+ ZEND_ARG_INFO(0, str_pattern)
+ ZEND_ARG_INFO(0, i_count)
+ZEND_END_ARG_INFO();
+
#ifdef ZTS
ZEND_DECLARE_MODULE_GLOBALS(redis)
#endif
@@ -160,6 +179,7 @@ static zend_function_entry redis_functions[] = {
PHP_ME(Redis, object, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, bitop, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, bitcount, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Redis, bitpos, NULL, ZEND_ACC_PUBLIC)
/* 1.1 */
PHP_ME(Redis, mset, NULL, ZEND_ACC_PUBLIC)
@@ -228,10 +248,17 @@ static zend_function_entry redis_functions[] = {
PHP_ME(Redis, clearLastError, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, _prefix, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Redis, _serialize, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, _unserialize, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, client, NULL, ZEND_ACC_PUBLIC)
+ /* SCAN and friends */
+ PHP_ME(Redis, scan, arginfo_scan, ZEND_ACC_PUBLIC)
+ PHP_ME(Redis, hscan, arginfo_kscan, ZEND_ACC_PUBLIC)
+ PHP_ME(Redis, zscan, arginfo_kscan, ZEND_ACC_PUBLIC)
+ PHP_ME(Redis, sscan, arginfo_kscan, ZEND_ACC_PUBLIC)
+
/* options */
PHP_ME(Redis, getOption, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, setOption, NULL, ZEND_ACC_PUBLIC)
@@ -252,6 +279,9 @@ static zend_function_entry redis_functions[] = {
PHP_ME(Redis, getAuth, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, isConnected, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Redis, wait, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Redis, pubsub, NULL, ZEND_ACC_PUBLIC)
+
/* aliases */
PHP_MALIAS(Redis, open, connect, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, popen, pconnect, NULL, ZEND_ACC_PUBLIC)
@@ -488,6 +518,11 @@ PHP_MINIT_FUNCTION(redis)
/* serializer */
add_constant_long(redis_ce, "SERIALIZER_NONE", REDIS_SERIALIZER_NONE);
add_constant_long(redis_ce, "SERIALIZER_PHP", REDIS_SERIALIZER_PHP);
+
+ /* scan options*/
+ add_constant_long(redis_ce, "OPT_SCAN", REDIS_OPT_SCAN);
+ add_constant_long(redis_ce, "SCAN_RETRY", REDIS_SCAN_RETRY);
+ add_constant_long(redis_ce, "SCAN_NORETRY", REDIS_SCAN_NORETRY);
#ifdef HAVE_REDIS_IGBINARY
add_constant_long(redis_ce, "SERIALIZER_IGBINARY", REDIS_SERIALIZER_IGBINARY);
#endif
@@ -790,6 +825,59 @@ PHP_METHOD(Redis, bitcount)
}
/* }}} */
+/* {{{ proto integer Redis::bitpos(string key, int bit, [int start], [int end]) */
+PHP_METHOD(Redis, bitpos)
+{
+ zval *object;
+ RedisSock *redis_sock;
+ char *key, *cmd;
+ int key_len, cmd_len, argc, key_free=0;
+ long bit, start, end;
+
+ argc = ZEND_NUM_ARGS();
+
+ if(zend_parse_method_parameters(argc TSRMLS_CC, getThis(), "Osl|ll",
+ &object, redis_ce, &key, &key_len, &bit,
+ &start, &end)==FAILURE)
+ {
+ RETURN_FALSE;
+ }
+
+ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) {
+ RETURN_FALSE;
+ }
+
+ // We can prevalidate the first argument
+ if(bit != 0 && bit != 1) {
+ RETURN_FALSE;
+ }
+
+ // Prefix our key
+ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
+
+ // Various command semantics
+ if(argc == 2) {
+ cmd_len = redis_cmd_format_static(&cmd, "BITPOS", "sd", key, key_len,
+ bit);
+ } else if(argc == 3) {
+ cmd_len = redis_cmd_format_static(&cmd, "BITPOS", "sdd", key, key_len,
+ bit, start);
+ } else {
+ cmd_len = redis_cmd_format_static(&cmd, "BITPOS", "sddd", key, key_len,
+ bit, start, end);
+ }
+
+ // Free our key if it was prefixed
+ if(key_free) efree(key);
+
+ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
+ IF_ATOMIC() {
+ redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
+ }
+ REDIS_PROCESS_RESPONSE(redis_long_response);
+}
+/* }}} */
+
/* {{{ proto boolean Redis::close()
*/
PHP_METHOD(Redis, close)
@@ -839,7 +927,9 @@ PHP_METHOD(Redis, set) {
/* Our optional argument can either be a long (to support legacy SETEX */
/* redirection), or an array with Redis >= 2.6.12 set options */
- if(z_opts && Z_TYPE_P(z_opts) != IS_LONG && Z_TYPE_P(z_opts) != IS_ARRAY) {
+ if(z_opts && Z_TYPE_P(z_opts) != IS_LONG && Z_TYPE_P(z_opts) != IS_ARRAY
+ && Z_TYPE_P(z_opts) != IS_NULL)
+ {
RETURN_FALSE;
}
@@ -903,7 +993,7 @@ PHP_METHOD(Redis, set) {
/* Free our key or value if we prefixed/serialized */
if(key_free) efree(key);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
/* Kick off the command */
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
@@ -936,7 +1026,7 @@ PHP_REDIS_API void redis_generic_setex(INTERNAL_FUNCTION_PARAMETERS, char *keywo
val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC);
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, keyword, "sls", key, key_len, expire, val, val_len);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
if(key_free) efree(key);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
@@ -985,7 +1075,7 @@ PHP_METHOD(Redis, setnx)
val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC);
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, "SETNX", "ss", key, key_len, val, val_len);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
if(key_free) efree(key);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
@@ -1023,7 +1113,7 @@ PHP_METHOD(Redis, getSet)
val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC);
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, "GETSET", "ss", key, key_len, val, val_len);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
if(key_free) efree(key);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
@@ -1331,12 +1421,10 @@ PHP_METHOD(Redis, incrByFloat) {
RETURN_FALSE;
}
- // Prefix our key, free it if we have
+ // Prefix key, format command, free old key if necissary
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
- if(key_free) efree(key);
-
- // Format our INCRBYFLOAT command
cmd_len = redis_cmd_format_static(&cmd, "INCRBYFLOAT", "sf", key, key_len, val);
+ if(key_free) efree(key);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
IF_ATOMIC() {
@@ -1742,6 +1830,11 @@ PHP_METHOD(Redis, getBit)
RETURN_FALSE;
}
+ // GETBIT and SETBIT only work for 0 - 2^32-1
+ if(offset < BITOP_MIN_OFFSET || offset > BITOP_MAX_OFFSET) {
+ RETURN_FALSE;
+ }
+
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, "GETBIT", "sd", key, key_len, (int)offset);
if(key_free) efree(key);
@@ -1771,6 +1864,11 @@ PHP_METHOD(Redis, setBit)
RETURN_FALSE;
}
+ // GETBIT and SETBIT only work for 0 - 2^32-1
+ if(offset < BITOP_MIN_OFFSET || offset > BITOP_MAX_OFFSET) {
+ RETURN_FALSE;
+ }
+
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, "SETBIT", "sdd", key, key_len, (int)offset, (int)val);
if(key_free) efree(key);
@@ -1833,7 +1931,7 @@ generic_push_function(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_l
val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC);
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, keyword, "ss", key, key_len, val, val_len);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
if(key_free) efree(key);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
@@ -1909,9 +2007,9 @@ PHP_METHOD(Redis, lInsert)
val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC);
pivot_free = redis_serialize(redis_sock, z_pivot, &pivot, &pivot_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, "LINSERT", "ssss", key, key_len, position, position_len, pivot, pivot_len, val, val_len);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
if(key_free) efree(key);
- if(pivot_free) efree(pivot);
+ if(pivot_free) STR_FREE(pivot);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
IF_ATOMIC() {
@@ -2083,7 +2181,7 @@ PHP_METHOD(Redis, lRemove)
val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC);
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, "LREM", "sds", key, key_len, count, val, val_len);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
if(key_free) efree(key);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
@@ -2287,7 +2385,7 @@ PHP_METHOD(Redis, sMove)
src_free = redis_key_prefix(redis_sock, &src, &src_len TSRMLS_CC);
dst_free = redis_key_prefix(redis_sock, &dst, &dst_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, "SMOVE", "sss", src, src_len, dst, dst_len, val, val_len);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
if(src_free) efree(src);
if(dst_free) efree(dst);
@@ -2349,14 +2447,23 @@ PHP_METHOD(Redis, sRandMember)
// Process our command
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
- // Process our reply
+ // Either bulk or multi-bulk depending on argument count
+ if(ZEND_NUM_ARGS() == 2) {
IF_ATOMIC() {
- // This will be bulk or multi-bulk depending if we passed the optional [COUNT] argument
- if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL) < 0) {
+ if(redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,
+ redis_sock, NULL, NULL) < 0)
+ {
RETURN_FALSE;
}
}
- REDIS_PROCESS_RESPONSE(redis_read_variant_reply);
+ REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply);
+ } else {
+ IF_ATOMIC() {
+ redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
+ NULL, NULL);
+ }
+ REDIS_PROCESS_RESPONSE(redis_string_response);
+ }
}
/* }}} */
@@ -2384,7 +2491,7 @@ PHP_METHOD(Redis, sContains)
val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC);
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, "SISMEMBER", "ss", key, key_len, val, val_len);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
if(key_free) efree(key);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
@@ -2612,7 +2719,7 @@ PHP_REDIS_API int generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAMETERS, char *
/* cleanup prefixed keys. */
for(i = 0; i < real_argc + (has_timeout?-1:0); ++i) {
if(keys_to_free[i])
- efree(keys[i]);
+ STR_FREE(keys[i]);
}
if(single_array && has_timeout) { /* cleanup string created to contain timeout value */
efree(keys[real_argc-1]);
@@ -3258,7 +3365,7 @@ PHP_METHOD(Redis, lSet) {
val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC);
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, "LSET", "sds", key, key_len, index, val, val_len);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
if(key_free) efree(key);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
@@ -3678,7 +3785,7 @@ generic_mset(INTERNAL_FUNCTION_PARAMETERS, char *kw, void (*fun)(INTERNAL_FUNCTI
memcpy(p, _NL, 2); p += 2;
}
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
if(key_free) efree(key);
}
}
@@ -3861,7 +3968,7 @@ PHP_METHOD(Redis, zAdd) {
smart_str_appendl(&buf, val, val_len);
smart_str_appendl(&buf, _NL, sizeof(_NL) - 1);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
}
/* end string */
@@ -4258,7 +4365,7 @@ PHP_METHOD(Redis, zScore)
val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC);
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, "ZSCORE", "ss", key, key_len, val, val_len);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
if(key_free) efree(key);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
@@ -4291,7 +4398,7 @@ PHP_REDIS_API void generic_rank_method(INTERNAL_FUNCTION_PARAMETERS, char *keywo
val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC);
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, keyword, "ss", key, key_len, val, val_len);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
if(key_free) efree(key);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
@@ -4341,7 +4448,7 @@ PHP_REDIS_API void generic_incrby_method(INTERNAL_FUNCTION_PARAMETERS, char *key
val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC);
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, keyword, "sfs", key, key_len, add, val, val_len);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
if(key_free) efree(key);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
@@ -4556,7 +4663,7 @@ generic_hset(INTERNAL_FUNCTION_PARAMETERS, char *kw, void (*fun)(INTERNAL_FUNCTI
val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC);
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
cmd_len = redis_cmd_format_static(&cmd, kw, "sss", key, key_len, member, member_len, val, val_len);
- if(val_free) efree(val);
+ if(val_free) STR_FREE(val);
if(key_free) efree(key);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
@@ -5076,7 +5183,7 @@ PHP_METHOD(Redis, hMset)
redis_cmd_append_sstr(&set_cmds, hkey, hkey_len - 1);
redis_cmd_append_sstr(&set_cmds, hval, hval_len);
- if(hval_free) efree(hval);
+ if(hval_free) STR_FREE(hval);
}
// Now construct the entire command
@@ -5804,22 +5911,19 @@ PHP_METHOD(Redis, getOption) {
}
switch(option) {
-
case REDIS_OPT_SERIALIZER:
RETURN_LONG(redis_sock->serializer);
-
case REDIS_OPT_PREFIX:
if(redis_sock->prefix) {
RETURN_STRINGL(redis_sock->prefix, redis_sock->prefix_len, 1);
}
RETURN_NULL();
-
case REDIS_OPT_READ_TIMEOUT:
RETURN_DOUBLE(redis_sock->read_timeout);
-
+ case REDIS_OPT_SCAN:
+ RETURN_LONG(redis_sock->scan);
default:
RETURN_FALSE;
-
}
}
/* }}} */
@@ -5857,7 +5961,6 @@ PHP_METHOD(Redis, setOption) {
RETURN_FALSE;
}
break;
-
case REDIS_OPT_PREFIX:
if(redis_sock->prefix) {
efree(redis_sock->prefix);
@@ -5871,17 +5974,22 @@ PHP_METHOD(Redis, setOption) {
memcpy(redis_sock->prefix, val_str, val_len);
}
RETURN_TRUE;
-
case REDIS_OPT_READ_TIMEOUT:
redis_sock->read_timeout = atof(val_str);
if(redis_sock->stream) {
read_tv.tv_sec = (time_t)redis_sock->read_timeout;
read_tv.tv_usec = (int)((redis_sock->read_timeout - read_tv.tv_sec) * 1000000);
- php_stream_set_option(redis_sock->stream, PHP_STREAM_OPTION_READ_TIMEOUT,
- 0, &read_tv);
+ php_stream_set_option(redis_sock->stream, PHP_STREAM_OPTION_READ_TIMEOUT,0, &read_tv);
}
RETURN_TRUE;
-
+ case REDIS_OPT_SCAN:
+ val_long = atol(val_str);
+ if(val_long == REDIS_SCAN_NORETRY || val_long == REDIS_SCAN_RETRY) {
+ redis_sock->scan = val_long;
+ RETURN_TRUE;
+ }
+ RETURN_FALSE;
+ break;
default:
RETURN_FALSE;
}
@@ -5992,6 +6100,207 @@ PHP_METHOD(Redis, slowlog) {
REDIS_PROCESS_RESPONSE(redis_read_variant_reply);
}
+/* {{{ proto Redis::wait(int num_slaves, int ms) }}}
+ */
+PHP_METHOD(Redis, wait) {
+ zval *object;
+ RedisSock *redis_sock;
+ long num_slaves, timeout;
+ char *cmd;
+ int cmd_len;
+
+ // Make sure arguments are valid
+ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oll",
+ &object, redis_ce, &num_slaves, &timeout)
+ ==FAILURE)
+ {
+ RETURN_FALSE;
+ }
+
+ // Don't even send this to Redis if our args are negative
+ if(num_slaves < 0 || timeout < 0) {
+ RETURN_FALSE;
+ }
+
+ // Grab our socket
+ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0)<0) {
+ RETURN_FALSE;
+ }
+
+ // Construct the command
+ cmd_len = redis_cmd_format_static(&cmd, "WAIT", "ll", num_slaves, timeout);
+
+ // Kick it off
+ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
+ IF_ATOMIC() {
+ redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
+ }
+ REDIS_PROCESS_RESPONSE(redis_long_response);
+}
+
+/*
+ * Construct a PUBSUB command
+ */
+PHP_REDIS_API int
+redis_build_pubsub_cmd(RedisSock *redis_sock, char **ret, PUBSUB_TYPE type,
+ zval *arg TSRMLS_DC)
+{
+ HashTable *ht_chan;
+ HashPosition ptr;
+ zval **z_ele;
+ char *key;
+ int cmd_len, key_len, key_free;
+ smart_str cmd = {0};
+
+ if(type == PUBSUB_CHANNELS) {
+ if(arg) {
+ // Get string argument and length.
+ key = Z_STRVAL_P(arg);
+ key_len = Z_STRLEN_P(arg);
+
+ // Prefix if necissary
+ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
+
+ // With a pattern
+ cmd_len = redis_cmd_format_static(ret, "PUBSUB", "ss", "CHANNELS", sizeof("CHANNELS")-1,
+ key, key_len);
+
+ // Free the channel name if we prefixed it
+ if(key_free) efree(key);
+
+ // Return command length
+ return cmd_len;
+ } else {
+ // No pattern
+ return redis_cmd_format_static(ret, "PUBSUB", "s", "CHANNELS", sizeof("CHANNELS")-1);
+ }
+ } else if(type == PUBSUB_NUMSUB) {
+ ht_chan = Z_ARRVAL_P(arg);
+
+ // Add PUBSUB and NUMSUB bits
+ redis_cmd_init_sstr(&cmd, zend_hash_num_elements(ht_chan)+1, "PUBSUB", sizeof("PUBSUB")-1);
+ redis_cmd_append_sstr(&cmd, "NUMSUB", sizeof("NUMSUB")-1);
+
+ // Iterate our elements
+ for(zend_hash_internal_pointer_reset_ex(ht_chan, &ptr);
+ zend_hash_get_current_data_ex(ht_chan, (void**)&z_ele, &ptr)==SUCCESS;
+ zend_hash_move_forward_ex(ht_chan, &ptr))
+ {
+ char *key;
+ int key_len, key_free;
+ zval *z_tmp = NULL;
+
+ if(Z_TYPE_PP(z_ele) == IS_STRING) {
+ key = Z_STRVAL_PP(z_ele);
+ key_len = Z_STRLEN_PP(z_ele);
+ } else {
+ MAKE_STD_ZVAL(z_tmp);
+ *z_tmp = **z_ele;
+ zval_copy_ctor(z_tmp);
+ convert_to_string(z_tmp);
+
+ key = Z_STRVAL_P(z_tmp);
+ key_len = Z_STRLEN_P(z_tmp);
+ }
+
+ // Apply prefix if required
+ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
+
+ // Append this channel
+ redis_cmd_append_sstr(&cmd, key, key_len);
+
+ // Free key if prefixed
+ if(key_free) efree(key);
+
+ // Free our temp var if we converted from something other than a string
+ if(z_tmp) {
+ zval_dtor(z_tmp);
+ efree(z_tmp);
+ z_tmp = NULL;
+ }
+ }
+
+ // Set return
+ *ret = cmd.c;
+ return cmd.len;
+ } else if(type == PUBSUB_NUMPAT) {
+ return redis_cmd_format_static(ret, "PUBSUB", "s", "NUMPAT", sizeof("NUMPAT")-1);
+ }
+
+ // Shouldn't ever happen
+ return -1;
+}
+
+/*
+ * {{{ proto Redis::pubsub("channels", pattern);
+ * proto Redis::pubsub("numsub", Array channels);
+ * proto Redis::pubsub("numpat"); }}}
+ */
+PHP_METHOD(Redis, pubsub) {
+ zval *object;
+ RedisSock *redis_sock;
+ char *keyword, *cmd;
+ int kw_len, cmd_len;
+ PUBSUB_TYPE type;
+ zval *arg=NULL;
+
+ // Parse arguments
+ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|z",
+ &object, redis_ce, &keyword, &kw_len, &arg)
+ ==FAILURE)
+ {
+ RETURN_FALSE;
+ }
+
+ // Validate our sub command keyword, and that we've got proper arguments
+ if(!strncasecmp(keyword, "channels", sizeof("channels"))) {
+ // One (optional) string argument
+ if(arg && Z_TYPE_P(arg) != IS_STRING) {
+ RETURN_FALSE;
+ }
+ type = PUBSUB_CHANNELS;
+ } else if(!strncasecmp(keyword, "numsub", sizeof("numsub"))) {
+ // One array argument
+ if(ZEND_NUM_ARGS() < 2 || Z_TYPE_P(arg) != IS_ARRAY ||
+ zend_hash_num_elements(Z_ARRVAL_P(arg))==0)
+ {
+ RETURN_FALSE;
+ }
+ type = PUBSUB_NUMSUB;
+ } else if(!strncasecmp(keyword, "numpat", sizeof("numpat"))) {
+ type = PUBSUB_NUMPAT;
+ } else {
+ // Invalid keyword
+ RETURN_FALSE;
+ }
+
+ // Grab our socket context object
+ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0)<0) {
+ RETURN_FALSE;
+ }
+
+ // Construct our "PUBSUB" command
+ cmd_len = redis_build_pubsub_cmd(redis_sock, &cmd, type, arg TSRMLS_CC);
+
+ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
+
+ if(type == PUBSUB_NUMSUB) {
+ IF_ATOMIC() {
+ if(redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL)<0) {
+ RETURN_FALSE;
+ }
+ }
+ REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_zipped);
+ } else {
+ IF_ATOMIC() {
+ if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL)<0) {
+ RETURN_FALSE;
+ }
+ }
+ REDIS_PROCESS_RESPONSE(redis_read_variant_reply);
+ }
+}
+
// Construct an EVAL or EVALSHA command, with option argument array and number of arguments that are keys parameter
PHP_REDIS_API int
redis_build_eval_cmd(RedisSock *redis_sock, char **ret, char *keyword, char *value, int val_len, zval *args, int keys_count TSRMLS_DC) {
@@ -6291,18 +6600,20 @@ PHP_METHOD(Redis, restore) {
}
/*
- * {{{ proto Redis::migrate(host port key dest-db timeout)
+ * {{{ proto Redis::migrate(host port key dest-db timeout [bool copy, bool replace])
*/
PHP_METHOD(Redis, migrate) {
zval *object;
RedisSock *redis_sock;
char *cmd, *host, *key;
int cmd_len, host_len, key_len, key_free;
+ zend_bool copy=0, replace=0;
long port, dest_db, timeout;
// Parse arguments
- if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oslsll", &object, redis_ce,
- &host, &host_len, &port, &key, &key_len, &dest_db, &timeout) == FAILURE) {
+ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oslsll|bb", &object, redis_ce,
+ &host, &host_len, &port, &key, &key_len, &dest_db, &timeout,
+ &copy, &replace) == FAILURE) {
RETURN_FALSE;
}
@@ -6313,7 +6624,26 @@ PHP_METHOD(Redis, migrate) {
// Prefix our key if we need to, build our command
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
- cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsdd", host, host_len, port, key, key_len, dest_db, timeout);
+
+ // Construct our command
+ if(copy && replace) {
+ cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsddss", host, host_len, port,
+ key, key_len, dest_db, timeout, "COPY",
+ sizeof("COPY")-1, "REPLACE", sizeof("REPLACE")-1);
+ } else if(copy) {
+ cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsdds", host, host_len, port,
+ key, key_len, dest_db, timeout, "COPY",
+ sizeof("COPY")-1);
+ } else if(replace) {
+ cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsdds", host, host_len, port,
+ key, key_len, dest_db, timeout, "REPLACE",
+ sizeof("REPLACE")-1);
+ } else {
+ cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsdd", host, host_len, port,
+ key, key_len, dest_db, timeout);
+ }
+
+ // Free our key if we prefixed it
if(key_free) efree(key);
// Kick off our MIGRATE request
@@ -6353,6 +6683,36 @@ PHP_METHOD(Redis, _prefix) {
}
/*
+ * {{{ proto Redis::_serialize(value)
+ */
+PHP_METHOD(Redis, _serialize) {
+ zval *object;
+ RedisSock *redis_sock;
+ zval *z_val;
+ char *val;
+ int val_len;
+
+ // Parse arguments
+ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oz",
+ &object, redis_ce, &z_val) == FAILURE)
+ {
+ RETURN_FALSE;
+ }
+
+ // Grab socket
+ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) {
+ RETURN_FALSE;
+ }
+
+ // Serialize, which will return a value even if no serializer is set
+ redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC);
+
+ // Return serialized value. Tell PHP to make a copy as some can be interned.
+ RETVAL_STRINGL(val, val_len, 1);
+ STR_FREE(val);
+}
+
+/*
* {{{ proto Redis::_unserialize(value)
*/
PHP_METHOD(Redis, _unserialize) {
@@ -6634,4 +6994,169 @@ PHP_METHOD(Redis, client) {
}
}
+/**
+ * Helper to format any combination of SCAN arguments
+ */
+PHP_REDIS_API int
+redis_build_scan_cmd(char **cmd, REDIS_SCAN_TYPE type, char *key, int key_len,
+ int iter, char *pattern, int pattern_len, int count)
+{
+ char *keyword;
+ int arg_count, cmd_len;
+
+ // Count our arguments +1 for key if it's got one, and + 2 for pattern
+ // or count given that they each carry keywords with them.
+ arg_count = 1 + (key_len>0) + (pattern_len>0?2:0) + (count>0?2:0);
+
+ // Turn our type into a keyword
+ switch(type) {
+ case TYPE_SCAN:
+ keyword = "SCAN";
+ break;
+ case TYPE_SSCAN:
+ keyword = "SSCAN";
+ break;
+ case TYPE_HSCAN:
+ keyword = "HSCAN";
+ break;
+ case TYPE_ZSCAN:
+ default:
+ keyword = "ZSCAN";
+ break;
+ }
+
+ // Start the command
+ cmd_len = redis_cmd_format_header(cmd, keyword, arg_count);
+
+ // Add the key in question if we have one
+ if(key_len) {
+ cmd_len = redis_cmd_append_str(cmd, cmd_len, key, key_len);
+ }
+
+ // Add our iterator
+ cmd_len = redis_cmd_append_int(cmd, cmd_len, iter);
+
+ // Append COUNT if we've got it
+ if(count) {
+ cmd_len = redis_cmd_append_str(cmd, cmd_len, "COUNT", sizeof("COUNT")-1);
+ cmd_len = redis_cmd_append_int(cmd, cmd_len, count);
+ }
+
+ // Append MATCH if we've got it
+ if(pattern_len) {
+ cmd_len = redis_cmd_append_str(cmd, cmd_len, "MATCH", sizeof("MATCH")-1);
+ cmd_len = redis_cmd_append_str(cmd, cmd_len, pattern, pattern_len);
+ }
+
+ // Return our command length
+ return cmd_len;
+}
+
+/**
+ * {{{ proto redis::scan(&$iterator, [pattern, [count]])
+ */
+PHP_REDIS_API void
+generic_scan_cmd(INTERNAL_FUNCTION_PARAMETERS, REDIS_SCAN_TYPE type) {
+ zval *object, *z_iter;
+ RedisSock *redis_sock;
+ HashTable *hash;
+ char *pattern=NULL, *cmd, *key=NULL;
+ int cmd_len, key_len=0, pattern_len=0, num_elements, key_free=0;
+ long count=0, iter;
+
+ // Different prototype depending on if this is a key based scan
+ if(type != TYPE_SCAN) {
+ // Requires a key
+ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz/|s!l",
+ &object, redis_ce, &key, &key_len, &z_iter,
+ &pattern, &pattern_len, &count)==FAILURE)
+ {
+ RETURN_FALSE;
+ }
+ } else {
+ // Doesn't require a key
+ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oz/|s!l",
+ &object, redis_ce, &z_iter, &pattern, &pattern_len,
+ &count) == FAILURE)
+ {
+ RETURN_FALSE;
+ }
+ }
+
+ // Grab our socket
+ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) {
+ RETURN_FALSE;
+ }
+
+ // Calling this in a pipeline makes no sense
+ IF_NOT_ATOMIC() {
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "Can't call SCAN commands in multi or pipeline mode!");
+ RETURN_FALSE;
+ }
+
+ // The iterator should be passed in as NULL for the first iteration, but we can treat
+ // any NON LONG value as NULL for these purposes as we've seperated the variable anyway.
+ if(Z_TYPE_P(z_iter) != IS_LONG || Z_LVAL_P(z_iter)<0) {
+ // Convert to long
+ convert_to_long(z_iter);
+ iter = 0;
+ } else if(Z_LVAL_P(z_iter)!=0) {
+ // Update our iterator value for the next passthru
+ iter = Z_LVAL_P(z_iter);
+ } else {
+ // We're done, back to iterator zero
+ RETURN_FALSE;
+ }
+
+ // Prefix our key if we've got one and we have a prefix set
+ if(key_len) {
+ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
+ }
+
+ /**
+ * Redis can return to us empty keys, especially in the case where there are a large
+ * number of keys to scan, and we're matching against a pattern. PHPRedis can be set
+ * up to abstract this from the user, by setting OPT_SCAN to REDIS_SCAN_RETRY. Otherwise
+ * we will return empty keys and the user will need to make subsequent calls with
+ * an updated iterator.
+ */
+ do {
+ // Format our SCAN command
+ cmd_len = redis_build_scan_cmd(&cmd, type, key, key_len, (int)iter,
+ pattern, pattern_len, count);
+
+ // Execute our command getting our new iterator value
+ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
+ if(redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,
+ redis_sock,type,&iter)<0)
+ {
+ if(key_free) efree(key);
+ RETURN_FALSE;
+ }
+
+ // Get the number of elements
+ hash = Z_ARRVAL_P(return_value);
+ num_elements = zend_hash_num_elements(hash);
+ } while(redis_sock->scan == REDIS_SCAN_RETRY && iter != 0 && num_elements == 0);
+
+ // Free our key if it was prefixed
+ if(key_free) efree(key);
+
+ // Update our iterator reference
+ Z_LVAL_P(z_iter) = iter;
+}
+
+PHP_METHOD(Redis, scan) {
+ generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_SCAN);
+}
+PHP_METHOD(Redis, hscan) {
+ generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_HSCAN);
+}
+PHP_METHOD(Redis, sscan) {
+ generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_SSCAN);
+}
+PHP_METHOD(Redis, zscan) {
+ generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_ZSCAN);
+}
+
/* vim: set tabstop=4 softtabstops=4 noexpandtab shiftwidth=4: */
diff --git a/redis_array.c b/redis_array.c
index 60d1022b..e9a56e7a 100644
--- a/redis_array.c
+++ b/redis_array.c
@@ -32,6 +32,12 @@
#include "redis_array.h"
#include "redis_array_impl.h"
+/* Simple macro to detect failure in a RedisArray call */
+#define RA_CALL_FAILED(rv, cmd) \
+ ((Z_TYPE_P(rv) == IS_BOOL && Z_BVAL_P(rv) == 0) || \
+ (Z_TYPE_P(rv) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(rv)) == 0) || \
+ (Z_TYPE_P(rv) == IS_LONG && Z_LVAL_P(rv) == 0 && !strcasecmp(cmd, "TYPE"))) \
+
extern zend_class_entry *redis_ce;
zend_class_entry *redis_array_ce;
@@ -76,43 +82,50 @@ zend_function_entry redis_array_functions[] = {
{NULL, NULL, NULL}
};
-int le_redis_array;
-void redis_destructor_redis_array(zend_rsrc_list_entry * rsrc TSRMLS_DC)
-{
+static void redis_array_free(RedisArray *ra) {
int i;
- RedisArray *ra = (RedisArray*)rsrc->ptr;
- /* delete Redis objects */
- for(i = 0; i < ra->count; ++i) {
+ // Redis objects
+ for(i=0;i<ra->count;i++) {
zval_dtor(ra->redis[i]);
efree(ra->redis[i]);
-
- /* remove host too */
efree(ra->hosts[i]);
}
efree(ra->redis);
efree(ra->hosts);
- /* delete function */
+ /* delete hash function */
if(ra->z_fun) {
zval_dtor(ra->z_fun);
efree(ra->z_fun);
}
- /* delete distributor */
+ /* Distributor */
if(ra->z_dist) {
zval_dtor(ra->z_dist);
efree(ra->z_dist);
}
- /* delete list of pure commands */
+ /* Delete pur commands */
zval_dtor(ra->z_pure_cmds);
efree(ra->z_pure_cmds);
- /* free container */
+ // Free structure itself
efree(ra);
}
+int le_redis_array;
+void redis_destructor_redis_array(zend_rsrc_list_entry * rsrc TSRMLS_DC)
+{
+ RedisArray *ra = (RedisArray*)rsrc->ptr;
+
+ /* Free previous ring if it's set */
+ if(ra->prev) redis_array_free(ra->prev);
+
+ /* Free parent array */
+ redis_array_free(ra);
+}
+
/**
* redis_array_get
*/
@@ -199,6 +212,8 @@ PHP_METHOD(RedisArray, __construct)
long l_retry_interval = 0;
zend_bool b_lazy_connect = 0;
zval **z_retry_interval_pp;
+ double d_connect_timeout = 0;
+ zval **z_connect_timeout_pp;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|a", &z0, &z_opts) == FAILURE) {
RETURN_FALSE;
@@ -261,6 +276,18 @@ PHP_METHOD(RedisArray, __construct)
if(FAILURE != zend_hash_find(hOpts, "lazy_connect", sizeof("lazy_connect"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) {
b_lazy_connect = Z_BVAL_PP(zpData);
}
+
+ /* extract connect_timeout option */
+ if (FAILURE != zend_hash_find(hOpts, "connect_timeout", sizeof("connect_timeout"), (void**)&z_connect_timeout_pp)) {
+ if (Z_TYPE_PP(z_connect_timeout_pp) == IS_DOUBLE || Z_TYPE_PP(z_connect_timeout_pp) == IS_STRING) {
+ if (Z_TYPE_PP(z_connect_timeout_pp) == IS_DOUBLE) {
+ d_connect_timeout = Z_DVAL_PP(z_connect_timeout_pp);
+ }
+ else {
+ d_connect_timeout = atof(Z_STRVAL_PP(z_connect_timeout_pp));
+ }
+ }
+ }
}
/* extract either name of list of hosts from z0 */
@@ -270,7 +297,7 @@ PHP_METHOD(RedisArray, __construct)
break;
case IS_ARRAY:
- ra = ra_make_array(Z_ARRVAL_P(z0), z_fun, z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect TSRMLS_CC);
+ ra = ra_make_array(Z_ARRVAL_P(z0), z_fun, z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout TSRMLS_CC);
break;
default:
@@ -280,6 +307,8 @@ PHP_METHOD(RedisArray, __construct)
if(ra) {
ra->auto_rehash = b_autorehash;
+ ra->connect_timeout = d_connect_timeout;
+ if(ra->prev) ra->prev->auto_rehash = b_autorehash;
#if PHP_VERSION_ID >= 50400
id = zend_list_insert(ra, le_redis_array TSRMLS_CC);
#else
@@ -366,22 +395,14 @@ ra_forward_call(INTERNAL_FUNCTION_PARAMETERS, RedisArray *ra, const char *cmd, i
} else { /* call directly through. */
call_user_function(&redis_ce->function_table, &redis_inst, &z_fun, return_value, argc, z_callargs TSRMLS_CC);
- failed = 0;
- if((Z_TYPE_P(return_value) == IS_BOOL && Z_BVAL_P(return_value) == 0) ||
- (Z_TYPE_P(return_value) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(return_value)) == 0) ||
- (Z_TYPE_P(return_value) == IS_LONG && Z_LVAL_P(return_value) == 0 && !strcasecmp(cmd, "TYPE")))
-
- {
- failed = 1;
- }
-
/* check if we have an error. */
- if(failed && ra->prev && !b_write_cmd) { /* there was an error reading, try with prev ring. */
+ if(RA_CALL_FAILED(return_value,cmd) && ra->prev && !b_write_cmd) { /* there was an error reading, try with prev ring. */
/* ERROR, FALLBACK TO PREVIOUS RING and forward a reference to the first redis instance we were looking at. */
ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra->prev, cmd, cmd_len, z_args, z_new_target?z_new_target:redis_inst);
}
- if(!failed && !b_write_cmd && z_new_target && ra->auto_rehash) { /* move key from old ring to new ring */
+ /* Autorehash if the key was found on the previous node if this is a read command and auto rehashing is on */
+ if(!RA_CALL_FAILED(return_value,cmd) && !b_write_cmd && z_new_target && ra->auto_rehash) { /* move key from old ring to new ring */
ra_move_key(key, key_len, redis_inst, z_new_target TSRMLS_CC);
}
}
@@ -876,6 +897,9 @@ PHP_METHOD(RedisArray, mget)
/* calls */
for(n = 0; n < ra->count; ++n) { /* for each node */
+ /* We don't even need to make a call to this node if no keys go there */
+ if(!argc_each[n]) continue;
+
/* copy args for MGET call on node. */
MAKE_STD_ZVAL(z_argarray);
array_init(z_argarray);
diff --git a/redis_array.h b/redis_array.h
index 3b1163bf..2a8baf44 100644
--- a/redis_array.h
+++ b/redis_array.h
@@ -50,6 +50,7 @@ typedef struct RedisArray_ {
zval *z_fun; /* key extractor, callable */
zval *z_dist; /* key distributor, callable */
zval *z_pure_cmds; /* hash table */
+ double connect_timeout; /* socket connect timeout */
struct RedisArray_ *prev;
} RedisArray;
diff --git a/redis_array_impl.c b/redis_array_impl.c
index 10c7dc81..ef499e1a 100644
--- a/redis_array_impl.c
+++ b/redis_array_impl.c
@@ -44,7 +44,9 @@ ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b
/* init connections */
for(i = 0; i < count; ++i) {
- if(FAILURE == zend_hash_quick_find(hosts, NULL, 0, i, (void**)&zpData)) {
+ if(FAILURE == zend_hash_quick_find(hosts, NULL, 0, i, (void**)&zpData) ||
+ Z_TYPE_PP(zpData) != IS_STRING)
+ {
efree(ra);
return NULL;
}
@@ -70,7 +72,7 @@ ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b
call_user_function(&redis_ce->function_table, &ra->redis[i], &z_cons, &z_ret, 0, NULL TSRMLS_CC);
/* create socket */
- redis_sock = redis_sock_create(host, host_len, port, 0, ra->pconnect, NULL, retry_interval, b_lazy_connect);
+ redis_sock = redis_sock_create(host, host_len, port, ra->connect_timeout, ra->pconnect, NULL, retry_interval, b_lazy_connect);
if (!b_lazy_connect)
{
@@ -166,12 +168,14 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) {
zval *z_params_autorehash;
zval *z_params_retry_interval;
zval *z_params_pconnect;
+ zval *z_params_connect_timeout;
zval *z_params_lazy_connect;
RedisArray *ra = NULL;
zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0;
long l_retry_interval = 0;
zend_bool b_lazy_connect = 0;
+ double d_connect_timeout = 0;
HashTable *hHosts = NULL, *hPrev = NULL;
/* find entry */
@@ -258,7 +262,8 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) {
b_pconnect = 1;
}
}
- /* find retry interval option */
+
+ /* find lazy connect option */
MAKE_STD_ZVAL(z_params_lazy_connect);
array_init(z_params_lazy_connect);
sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.lazyconnect")), z_params_lazy_connect TSRMLS_CC);
@@ -268,9 +273,25 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) {
}
}
+ /* find connect timeout option */
+ MAKE_STD_ZVAL(z_params_connect_timeout);
+ array_init(z_params_connect_timeout);
+ sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.connecttimeout")), z_params_connect_timeout TSRMLS_CC);
+ if (zend_hash_find(Z_ARRVAL_P(z_params_connect_timeout), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) {
+ if (Z_TYPE_PP(z_data_pp) == IS_DOUBLE || Z_TYPE_PP(z_data_pp) == IS_STRING) {
+ if (Z_TYPE_PP(z_data_pp) == IS_DOUBLE) {
+ d_connect_timeout = Z_DVAL_PP(z_data_pp);
+ }
+ else {
+ d_connect_timeout = atof(Z_STRVAL_PP(z_data_pp));
+ }
+ }
+ }
+
/* create RedisArray object */
- ra = ra_make_array(hHosts, z_fun, z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect TSRMLS_CC);
+ ra = ra_make_array(hHosts, z_fun, z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout TSRMLS_CC);
ra->auto_rehash = b_autorehash;
+ if(ra->prev) ra->prev->auto_rehash = b_autorehash;
/* cleanup */
zval_dtor(z_params_hosts);
@@ -287,6 +308,8 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) {
efree(z_params_retry_interval);
zval_dtor(z_params_pconnect);
efree(z_params_pconnect);
+ zval_dtor(z_params_connect_timeout);
+ efree(z_params_connect_timeout);
zval_dtor(z_params_lazy_connect);
efree(z_params_lazy_connect);
@@ -294,7 +317,7 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) {
}
RedisArray *
-ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC) {
+ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout TSRMLS_DC) {
int count = zend_hash_num_elements(hosts);
@@ -308,6 +331,8 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev
ra->z_multi_exec = NULL;
ra->index = b_index;
ra->auto_rehash = 0;
+ ra->pconnect = b_pconnect;
+ ra->connect_timeout = connect_timeout;
/* init array data structures */
ra_init_function_table(ra);
@@ -315,7 +340,7 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev
if(NULL == ra_load_hosts(ra, hosts, retry_interval, b_lazy_connect TSRMLS_CC)) {
return NULL;
}
- ra->prev = hosts_prev ? ra_make_array(hosts_prev, z_fun, z_dist, NULL, b_index, b_pconnect, retry_interval, b_lazy_connect TSRMLS_CC) : NULL;
+ ra->prev = hosts_prev ? ra_make_array(hosts_prev, z_fun, z_dist, NULL, b_index, b_pconnect, retry_interval, b_lazy_connect, connect_timeout TSRMLS_CC) : NULL;
/* copy function if provided */
if(z_fun) {
@@ -603,6 +628,7 @@ ra_index_key(const char *key, int key_len, zval *z_redis TSRMLS_DC) {
/* don't dtor z_ret, since we're returning z_redis */
efree(z_args[0]);
+ zval_dtor(z_args[1]);
efree(z_args[1]);
}
@@ -965,6 +991,7 @@ ra_move_string(const char *key, int key_len, zval *z_from, zval *z_to, long ttl
ZVAL_STRINGL(z_args[0], key, key_len, 0);
ZVAL_LONG(z_args[1], ttl);
ZVAL_STRINGL(z_args[2], Z_STRVAL(z_ret), Z_STRLEN(z_ret), 1); /* copy z_ret to arg 1 */
+ zval_dtor(&z_ret); /* free memory from our previous call */
call_user_function(&redis_ce->function_table, &z_to, &z_fun_set, &z_ret, 3, z_args TSRMLS_CC);
/* cleanup */
efree(z_args[1]);
@@ -975,6 +1002,7 @@ ra_move_string(const char *key, int key_len, zval *z_from, zval *z_to, long ttl
ZVAL_STRINGL(&z_fun_set, "SET", 3, 0);
ZVAL_STRINGL(z_args[0], key, key_len, 0);
ZVAL_STRINGL(z_args[1], Z_STRVAL(z_ret), Z_STRLEN(z_ret), 1); /* copy z_ret to arg 1 */
+ zval_dtor(&z_ret); /* free memory from our previous return value */
call_user_function(&redis_ce->function_table, &z_to, &z_fun_set, &z_ret, 2, z_args TSRMLS_CC);
/* cleanup */
zval_dtor(z_args[1]);
diff --git a/redis_array_impl.h b/redis_array_impl.h
index 8f106542..06b5332a 100644
--- a/redis_array_impl.h
+++ b/redis_array_impl.h
@@ -12,7 +12,7 @@
RedisArray *ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC);
RedisArray *ra_load_array(const char *name TSRMLS_DC);
-RedisArray *ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC);
+RedisArray *ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout TSRMLS_DC);
zval *ra_find_node_by_name(RedisArray *ra, const char *host, int host_len TSRMLS_DC);
zval *ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_DC);
void ra_init_function_table(RedisArray *ra);
diff --git a/rpm/php-redis.spec b/rpm/php-redis.spec
index 4f04fb18..5363d1ee 100644
--- a/rpm/php-redis.spec
+++ b/rpm/php-redis.spec
@@ -3,7 +3,7 @@
%global php_version %(php-config --version 2>/dev/null || echo 0)
Name: php-redis
-Version: 2.2.4
+Version: 2.2.5
Release: 1%{?dist}
Summary: The phpredis extension provides an API for communicating with the Redis key-value store.
diff --git a/tests/TestRedis.php b/tests/TestRedis.php
index 2a684754..ad577202 100644
--- a/tests/TestRedis.php
+++ b/tests/TestRedis.php
@@ -72,6 +72,47 @@ class Redis_Test extends TestSuite
$this->assertTrue(is_array($ret) && count($ret) === 1 && $ret[0] >= 0);
}
+ // Run some simple tests against the PUBSUB command. This is problematic, as we
+ // can't be sure what's going on in the instance, but we can do some things.
+ public function testPubSub() {
+ // Only available since 2.8.0
+ if(version_compare($this->version, "2.8.0", "lt")) {
+ $this->markTestSkipped();
+ return;
+ }
+
+ // PUBSUB CHANNELS ...
+ $result = $this->redis->pubsub("channels", "*");
+ $this->assertTrue(is_array($result));
+ $result = $this->redis->pubsub("channels");
+ $this->assertTrue(is_array($result));
+
+ // PUBSUB NUMSUB
+
+ $c1 = uniqid() . '-' . rand(1,100);
+ $c2 = uniqid() . '-' . rand(1,100);
+
+ $result = $this->redis->pubsub("numsub", Array($c1, $c2));
+
+ // Should get an array back, with two elements
+ $this->assertTrue(is_array($result));
+ $this->assertEquals(count($result), 2);
+
+ // Make sure the elements are correct, and have zero counts
+ foreach(Array($c1,$c2) as $channel) {
+ $this->assertTrue(isset($result[$channel]));
+ $this->assertEquals($result[$channel], "0");
+ }
+
+ // PUBSUB NUMPAT
+ $result = $this->redis->pubsub("numpat");
+ $this->assertTrue(is_int($result));
+
+ // Invalid calls
+ $this->assertFalse($this->redis->pubsub("notacommand"));
+ $this->assertFalse($this->redis->pubsub("numsub", "not-an-array"));
+ }
+
public function testBitsets() {
$this->redis->delete('key');
@@ -110,6 +151,31 @@ class Redis_Test extends TestSuite
// values above 1 are changed to 1 but don't overflow on bits to the right.
$this->assertTrue(0 === $this->redis->setBit('key', 0, 0xff));
$this->assertTrue("\x9f" === $this->redis->get('key'));
+
+ // Verify valid offset ranges
+ $this->assertFalse($this->redis->getBit('key', -1));
+ $this->assertFalse($this->redis->getBit('key', 4294967296));
+ $this->assertFalse($this->redis->setBit('key', -1, 1));
+ $this->assertFalse($this->redis->setBit('key', 4294967296, 1));
+ }
+
+ public function testBitPos() {
+ if(version_compare($this->version, "2.8.7", "lt")) {
+ $this->MarkTestSkipped();
+ return;
+ }
+
+ $this->redis->del('bpkey');
+
+ $this->redis->set('bpkey', "\xff\xf0\x00");
+ $this->assertEquals($this->redis->bitpos('bpkey', 0), 12);
+
+ $this->redis->set('bpkey', "\x00\xff\xf0");
+ $this->assertEquals($this->redis->bitpos('bpkey', 1, 0), 8);
+ $this->assertEquals($this->redis->bitpos('bpkey', 1, 1), 8);
+
+ $this->redis->set('bpkey', "\x00\x00\x00");
+ $this->assertEquals($this->redis->bitpos('bpkey', 1), -1);
}
public function test1000() {
@@ -258,6 +324,12 @@ class Redis_Test extends TestSuite
$this->assertTrue($this->redis->set('foo','barbaz', Array('not-valid','nx','invalid','ex'=>200)));
$this->assertEquals($this->redis->ttl('foo'), 200);
$this->assertEquals($this->redis->get('foo'), 'barbaz');
+
+ /* Pass NULL as the optional arguments which should be ignored */
+ $this->redis->del('foo');
+ $this->redis->set('foo','bar', NULL);
+ $this->assertEquals($this->redis->get('foo'), 'bar');
+ $this->assertTrue($this->redis->ttl('foo')<0);
}
public function testGetSet() {
@@ -475,6 +547,16 @@ class Redis_Test extends TestSuite
$this->redis->incrbyfloat('key', -1.5);
$this->assertTrue("abc" === $this->redis->get('key'));
+
+ // Test with prefixing
+ $this->redis->setOption(Redis::OPT_PREFIX, 'someprefix:');
+ $this->redis->del('key');
+ $this->redis->incrbyfloat('key',1.8);
+ $this->assertEquals('1.8', $this->redis->get('key'));
+ $this->redis->setOption(Redis::OPT_PREFIX, '');
+ $this->assertTrue($this->redis->exists('someprefix:key'));
+ $this->redis->del('someprefix:key');
+
}
public function testDecr()
@@ -1144,6 +1226,28 @@ class Redis_Test extends TestSuite
break;
}
}
+
+ //
+ // With and without count, while serializing
+ //
+
+ $this->redis->delete('set0');
+ $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
+ for($i=0;$i<5;$i++) {
+ $member = "member:$i";
+ $this->redis->sAdd('set0', $member);
+ $mems[] = $member;
+ }
+
+ $member = $this->redis->srandmember('set0');
+ $this->assertTrue(in_array($member, $mems));
+
+ $rmembers = $this->redis->srandmember('set0', $i);
+ foreach($rmembers as $reply_mem) {
+ $this->assertTrue(in_array($reply_mem, $mems));
+ }
+
+ $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE);
}
public function testSRandMemberWithCount() {
@@ -1759,6 +1863,34 @@ class Redis_Test extends TestSuite
$this->assertFalse($this->redis->slowlog('notvalid'));
}
+ public function testWait() {
+ // Closest we can check based on redis commmit history
+ if(version_compare($this->version, '2.9.11', 'lt')) {
+ $this->markTestSkipped();
+ return;
+ }
+
+ // We could have slaves here, so determine that
+ $arr_slaves = $this->redis->info();
+ $i_slaves = $arr_slaves['connected_slaves'];
+
+ // Send a couple commands
+ $this->redis->set('wait-foo', 'over9000');
+ $this->redis->set('wait-bar', 'revo9000');
+
+ // Make sure we get the right replication count
+ $this->assertEquals($this->redis->wait($i_slaves, 100), $i_slaves);
+
+ // Pass more slaves than are connected
+ $this->redis->set('wait-foo','over9000');
+ $this->redis->set('wait-bar','revo9000');
+ $this->assertTrue($this->redis->wait($i_slaves+1, 100) < $i_slaves+1);
+
+ // Make sure when we pass with bad arguments we just get back false
+ $this->assertFalse($this->redis->wait(-1, -1));
+ $this->assertFalse($this->redis->wait(-1, 20));
+ }
+
public function testinfo() {
$info = $this->redis->info();
@@ -4356,6 +4488,34 @@ class Redis_Test extends TestSuite
$this->assertTrue(1 === $this->redis->evalsha($sha));
}
+ public function testSerialize() {
+ $vals = Array(1, 1.5, 'one', Array('here','is','an','array'));
+
+ // Test with no serialization at all
+ $this->assertTrue($this->redis->_serialize('test') === 'test');
+ $this->assertTrue($this->redis->_serialize(1) === '1');
+ $this->assertTrue($this->redis->_serialize(Array()) === 'Array');
+ $this->assertTrue($this->redis->_serialize(new stdClass) === 'Object');
+
+ $arr_serializers = Array(Redis::SERIALIZER_PHP);
+ if(defined('Redis::SERIALIZER_IGBINARY')) {
+ $arr_serializers[] = Redis::SERIALIZER_IGBINARY;
+ }
+
+ foreach($arr_serializers as $mode) {
+ $arr_enc = Array();
+ $arr_dec = Array();
+
+ foreach($vals as $k => $v) {
+ $enc = $this->redis->_serialize($v);
+ $dec = $this->redis->_unserialize($enc);
+
+ // They should be the same
+ $this->assertTrue($enc == $dec);
+ }
+ }
+ }
+
public function testUnserialize() {
$vals = Array(
1,1.5,'one',Array('this','is','an','array')
@@ -4462,6 +4622,190 @@ class Redis_Test extends TestSuite
$this->assertTrue($this->redis->getAuth() === self::AUTH);
}
+ /**
+ * Scan and variants
+ */
+
+ protected function get_keyspace_count($str_db) {
+ $arr_info = $this->redis->info();
+ $arr_info = $arr_info[$str_db];
+ $arr_info = explode(',', $arr_info);
+ $arr_info = explode('=', $arr_info[0]);
+ return $arr_info[1];
+ }
+
+ public function testScan() {
+ if(version_compare($this->version, "2.8.0", "lt")) {
+ $this->markTestSkipped();
+ return;
+ }
+
+ // Key count
+ $i_key_count = $this->get_keyspace_count('db0');
+
+ // Have scan retry
+ $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
+
+ // Scan them all
+ $it = NULL;
+ while($arr_keys = $this->redis->scan($it)) {
+ $i_key_count -= count($arr_keys);
+ }
+ // Should have iterated all keys
+ $this->assertEquals(0, $i_key_count);
+
+ // Unique keys, for pattern matching
+ $str_uniq = uniqid() . '-' . uniqid();
+ for($i=0;$i<10;$i++) {
+ $this->redis->set($str_uniq . "::$i", "bar::$i");
+ }
+
+ // Scan just these keys using a pattern match
+ $it = NULL;
+ while($arr_keys = $this->redis->scan($it, "*$str_uniq*")) {
+ $i -= count($arr_keys);
+ }
+ $this->assertEquals(0, $i);
+ }
+
+ public function testHScan() {
+ if(version_compare($this->version, "2.8.0", "lt")) {
+ $this->markTestSkipped();
+ return;
+ }
+
+ // Never get empty sets
+ $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
+
+ $this->redis->del('hash');
+ $i_foo_mems = 0;
+
+ for($i=0;$i<100;$i++) {
+ if($i>3) {
+ $this->redis->hset('hash', "member:$i", "value:$i");
+ } else {
+ $this->redis->hset('hash', "foomember:$i", "value:$i");
+ $i_foo_mems++;
+ }
+ }
+
+ // Scan all of them
+ $it = NULL;
+ while($arr_keys = $this->redis->hscan('hash', $it)) {
+ $i -= count($arr_keys);
+ }
+ $this->assertEquals(0, $i);
+
+ // Scan just *foomem* (should be 4)
+ $it = NULL;
+ while($arr_keys = $this->redis->hscan('hash', $it, '*foomember*')) {
+ $i_foo_mems -= count($arr_keys);
+ foreach($arr_keys as $str_mem => $str_val) {
+ $this->assertTrue(strpos($str_mem, 'member')!==FALSE);
+ $this->assertTrue(strpos($str_val, 'value')!==FALSE);
+ }
+ }
+ $this->assertEquals(0, $i_foo_mems);
+ }
+
+ public function testSScan() {
+ if(version_compare($this->version, "2.8.0", "lt")) {
+ $this->markTestSkipped();
+ return;
+ }
+
+ $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
+
+ $this->redis->del('set');
+ for($i=0;$i<100;$i++) {
+ $this->redis->sadd('set', "member:$i");
+ }
+
+ // Scan all of them
+ $it = NULL;
+ while($arr_keys = $this->redis->sscan('set', $it)) {
+ $i -= count($arr_keys);
+ foreach($arr_keys as $str_mem) {
+ $this->assertTrue(strpos($str_mem,'member')!==FALSE);
+ }
+ }
+ $this->assertEquals(0, $i);
+
+ // Scan just ones with zero in them (0, 10, 20, 30, 40, 50, 60, 70, 80, 90)
+ $it = NULL;
+ $i_w_zero = 0;
+ while($arr_keys = $this->redis->sscan('set', $it, '*0*')) {
+ $i_w_zero += count($arr_keys);
+ }
+ $this->assertEquals(10, $i_w_zero);
+ }
+
+ public function testZScan() {
+ if(version_compare($this->version, "2.8.0", "lt")) {
+ $this->markTestSkipped();
+ return;
+ }
+
+ $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
+
+ $this->redis->del('zset');
+ $i_tot_score = 0;
+ $i_p_score = 0;
+ $i_p_count = 0;
+ for($i=0;$i<2000;$i++) {
+ if($i<10) {
+ $this->redis->zadd('zset', $i, "pmem:$i");
+ $i_p_score += $i;
+ $i_p_count += 1;
+ } else {
+ $this->redis->zadd('zset', $i, "mem:$i");
+ }
+
+ $i_tot_score += $i;
+ }
+
+ // Scan them all
+ $it = NULL;
+ while($arr_keys = $this->redis->zscan('zset', $it)) {
+ foreach($arr_keys as $str_mem => $f_score) {
+ $i_tot_score -= $f_score;
+ $i--;
+ }
+ }
+ $this->assertEquals(0, $i);
+ $this->assertEquals(0, $i_tot_score);
+
+ // Just scan "pmem" members
+ $it = NULL;
+ $i_p_score_old = $i_p_score;
+ $i_p_count_old = $i_p_count;
+ while($arr_keys = $this->redis->zscan('zset', $it, "*pmem*")) {
+ foreach($arr_keys as $str_mem => $f_score) {
+ $i_p_score -= $f_score;
+ $i_p_count -= 1;
+ }
+ }
+ $this->assertEquals(0, $i_p_score);
+ $this->assertEquals(0, $i_p_count);
+
+ // Turn off retrying and we should get some empty results
+ $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_NORETRY);
+ $i_skips = 0;
+ $i_p_score = $i_p_score_old;
+ $i_p_count = $i_p_count_old;
+ $it = NULL;
+ while(($arr_keys = $this->redis->zscan('zset', $it, "*pmem*")) !== FALSE) {
+ if(count($arr_keys) == 0) $i_skips++;
+ foreach($arr_keys as $str_mem => $f_score) {
+ $i_p_score -= $f_score;
+ $i_p_count -= 1;
+ }
+ }
+ // We should still get all the keys, just with several empty results
+ $this->assertTrue($i_skips > 0);
+ $this->assertEquals(0, $i_p_score);
+ $this->assertEquals(0, $i_p_count);
+ }
}
exit(TestSuite::run("Redis_Test"));