diff options
author | michael-grunder <michael.grunder@gmail.com> | 2018-01-17 20:36:38 +0300 |
---|---|---|
committer | michael-grunder <michael.grunder@gmail.com> | 2018-01-17 20:36:38 +0300 |
commit | 9e65c429318d93bd37c9d42abf79709395e6e805 (patch) | |
tree | 379dc0f44cb4b7219eab0c4c31184f734f1d881d | |
parent | 837dee471ccac86581d306594ee945e82027572f (diff) |
Implement UNLINK command
This commit implements UNLINK for Redis, RedisCluster, and RedisArray.
To a client library UNLINK behaves identically to DEL so we can use the
same handlers for both.
-rw-r--r-- | php_redis.h | 1 | ||||
-rw-r--r-- | redis.c | 9 | ||||
-rw-r--r-- | redis_array.c | 33 | ||||
-rw-r--r-- | redis_array.h | 1 | ||||
-rw-r--r-- | redis_cluster.c | 24 | ||||
-rw-r--r-- | redis_cluster.h | 1 | ||||
-rw-r--r-- | redis_commands.c | 8 | ||||
-rw-r--r-- | redis_commands.h | 3 | ||||
-rw-r--r-- | tests/RedisArrayTest.php | 55 | ||||
-rw-r--r-- | tests/RedisTest.php | 65 |
10 files changed, 154 insertions, 46 deletions
diff --git a/php_redis.h b/php_redis.h index 895ad710..d1989cfc 100644 --- a/php_redis.h +++ b/php_redis.h @@ -46,6 +46,7 @@ PHP_METHOD(Redis, renameNx); PHP_METHOD(Redis, getMultiple); PHP_METHOD(Redis, exists); PHP_METHOD(Redis, delete); +PHP_METHOD(Redis, unlink); PHP_METHOD(Redis, incr); PHP_METHOD(Redis, incrBy); PHP_METHOD(Redis, incrByFloat); @@ -390,6 +390,7 @@ static zend_function_entry redis_functions[] = { PHP_ME(Redis, time, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(Redis, ttl, arginfo_key, ZEND_ACC_PUBLIC) PHP_ME(Redis, type, arginfo_key, ZEND_ACC_PUBLIC) + PHP_ME(Redis, unlink, arginfo_nkeys, ZEND_ACC_PUBLIC) PHP_ME(Redis, unsubscribe, arginfo_unsubscribe, ZEND_ACC_PUBLIC) PHP_ME(Redis, unwatch, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(Redis, wait, arginfo_wait, ZEND_ACC_PUBLIC) @@ -1176,6 +1177,13 @@ PHP_METHOD(Redis, delete) } /* }}} */ +/* {{{ proto long Redis::unlink(string $key1, string $key2 [, string $key3...]) }}} + * {{{ proto long Redis::unlink(array $keys) */ +PHP_METHOD(Redis, unlink) + + REDIS_PROCESS_CMD(unlink, redis_long_response); +} + PHP_REDIS_API void redis_set_watch(RedisSock *redis_sock) { redis_sock->watching = 1; @@ -1512,6 +1520,7 @@ PHP_METHOD(Redis, sDiffStore) { } /* }}} */ + /* {{{ proto array Redis::sort(string key, array options) */ PHP_METHOD(Redis, sort) { char *cmd; diff --git a/redis_array.c b/redis_array.c index 01a48e5f..4d70b50a 100644 --- a/redis_array.c +++ b/redis_array.c @@ -122,6 +122,7 @@ zend_function_entry redis_array_functions[] = { PHP_ME(RedisArray, save, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, select, arginfo_select, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, setOption,arginfo_setopt, ZEND_ACC_PUBLIC) + PHP_ME(RedisArray, unlink, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, unwatch, arginfo_void, ZEND_ACC_PUBLIC) PHP_MALIAS(RedisArray, delete, del, arginfo_del, ZEND_ACC_PUBLIC) PHP_MALIAS(RedisArray, getMultiple, mget, arginfo_mget, ZEND_ACC_PUBLIC) @@ -849,7 +850,7 @@ PHP_METHOD(RedisArray, select) zval_dtor(&z_fun); } #if (PHP_MAJOR_VERSION < 7) -#define HANDLE_MULTI_EXEC(ra, cmd) do { \ +#define HANDLE_MULTI_EXEC(ra, cmd, cmdlen) do { \ if (ra && ra->z_multi_exec) { \ int i, num_varargs;\ zval ***varargs = NULL, *z_arg_array; \ @@ -867,7 +868,7 @@ PHP_METHOD(RedisArray, select) add_next_index_zval(z_arg_array, z_tmp); \ }\ /* call */\ - ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra, cmd, sizeof(cmd) - 1, z_arg_array, NULL); \ + ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra, cmd, cmdlen, z_arg_array, NULL); \ zval_ptr_dtor(&z_arg_array); \ if(varargs) {\ efree(varargs);\ @@ -892,7 +893,7 @@ PHP_METHOD(RedisArray, select) add_next_index_zval(&z_arg_array, &z_tmp); \ } \ /* call */\ - ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra, cmd, sizeof(cmd) - 1, &z_arg_array, NULL); \ + ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra, cmd, cmdlen, &z_arg_array, NULL); \ zval_dtor(&z_arg_array); \ return; \ } \ @@ -914,7 +915,7 @@ PHP_METHOD(RedisArray, mget) } /* Multi/exec support */ - HANDLE_MULTI_EXEC(ra, "MGET"); + HANDLE_MULTI_EXEC(ra, "MGET", sizeof("MGET") - 1); if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", &object, redis_array_ce, &z_keys) == FAILURE) { @@ -1068,7 +1069,7 @@ PHP_METHOD(RedisArray, mset) } /* Multi/exec support */ - HANDLE_MULTI_EXEC(ra, "MSET"); + HANDLE_MULTI_EXEC(ra, "MSET", sizeof("MSET") - 1); if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", &object, redis_array_ce, &z_keys) == FAILURE) @@ -1179,9 +1180,8 @@ PHP_METHOD(RedisArray, mset) RETURN_TRUE; } -/* DEL will distribute the call to several nodes and regroup the values. */ -PHP_METHOD(RedisArray, del) -{ +/* Generic handler for DEL or UNLINK which behave identically to phpredis */ +static void ra_generic_del(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len) { zval *object, z_keys, z_fun, *data, z_ret, *z_tmp, *z_args; int i, n; RedisArray *ra; @@ -1194,8 +1194,9 @@ PHP_METHOD(RedisArray, del) if ((ra = redis_array_get(getThis() TSRMLS_CC)) == NULL) { RETURN_FALSE; } + /* Multi/exec support */ - HANDLE_MULTI_EXEC(ra, "DEL"); + HANDLE_MULTI_EXEC(ra, kw, kw_len); /* get all args in z_args */ z_args = emalloc(argc * sizeof(zval)); @@ -1259,7 +1260,7 @@ PHP_METHOD(RedisArray, del) } ZEND_HASH_FOREACH_END(); /* prepare call */ - ZVAL_STRINGL(&z_fun, "DEL", 3); + ZVAL_STRINGL(&z_fun, kw, kw_len); /* calls */ for(n = 0; n < ra->count; ++n) { /* for each node */ @@ -1285,7 +1286,7 @@ PHP_METHOD(RedisArray, del) found++; } - if(!found) { /* don't run empty DELs */ + if(!found) { /* don't run empty DEL or UNLINK commands */ zval_dtor(&z_argarray); continue; } @@ -1322,6 +1323,16 @@ PHP_METHOD(RedisArray, del) RETURN_LONG(total); } +/* DEL will distribute the call to several nodes and regroup the values. */ +PHP_METHOD(RedisArray, del) +{ + ra_generic_del(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DEL", sizeof("DEL")-1); +} + +PHP_METHOD(RedisArray, unlink) { + ra_generic_del(INTERNAL_FUNCTION_PARAM_PASSTHRU, "UNLINK", sizeof("UNLINK") - 1); +} + PHP_METHOD(RedisArray, multi) { zval *object; diff --git a/redis_array.h b/redis_array.h index d6a00fa0..7bacef41 100644 --- a/redis_array.h +++ b/redis_array.h @@ -25,6 +25,7 @@ PHP_METHOD(RedisArray, flushall); PHP_METHOD(RedisArray, mget); PHP_METHOD(RedisArray, mset); PHP_METHOD(RedisArray, del); +PHP_METHOD(RedisArray, unlink); PHP_METHOD(RedisArray, keys); PHP_METHOD(RedisArray, getOption); PHP_METHOD(RedisArray, setOption); diff --git a/redis_cluster.c b/redis_cluster.c index 95bcba40..ede16b94 100644 --- a/redis_cluster.c +++ b/redis_cluster.c @@ -244,6 +244,7 @@ zend_function_entry redis_cluster_functions[] = { PHP_ME(RedisCluster, ttl, arginfo_key, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, type, arginfo_key, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, unsubscribe, arginfo_unsubscribe, ZEND_ACC_PUBLIC) + PHP_ME(RedisCluster, unlink, arginfo_del, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, unwatch, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, watch, arginfo_watch, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zadd, arginfo_zadd, ZEND_ACC_PUBLIC) @@ -708,7 +709,8 @@ static HashTable *method_args_to_ht(zval *z_args, int argc) { return ht_ret; } -/* Handler for both MGET and DEL */ +/* Convienience handler for commands that take multiple keys such as + * MGET, DEL, and UNLINK */ static int cluster_mkey_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len, zval *z_ret, cluster_cb cb) { @@ -935,8 +937,10 @@ static int cluster_mset_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len, return 0; } -/* {{{ proto array RedisCluster::del(string key1, string key2, ... keyN) */ -PHP_METHOD(RedisCluster, del) { +/* Generic passthru for DEL and UNLINK which act identically */ +static void cluster_generic_delete(INTERNAL_FUNCTION_PARAMETERS, + char *kw, int kw_len) +{ zval *z_ret; #if (PHP_MAJOR_VERSION < 7) @@ -949,14 +953,24 @@ PHP_METHOD(RedisCluster, del) { ZVAL_LONG(z_ret, 0); // Parse args, process - if(cluster_mkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DEL", - sizeof("DEL")-1, z_ret, cluster_del_resp)<0) + if(cluster_mkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, kw, kw_len, z_ret, + cluster_del_resp) < 0) { efree(z_ret); RETURN_FALSE; } } +/* {{{ proto array RedisCluster::del(string key1, string key2, ... keyN) */ +PHP_METHOD(RedisCluster, del) { + cluster_generic_delete(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DEL", sizeof("DEL") - 1); +} + +/* {{{ proto array RedisCluster::unlink(string key1, string key2, ... keyN) */ +PHP_METHOD(RedisCluster, unlink) { + cluster_generic_delete(INTERNAL_FUNCTION_PARAM_PASSTHRU, "UNLINK", sizeof("UNLINK") - 1); +} + /* {{{ proto array RedisCluster::mget(array keys) */ PHP_METHOD(RedisCluster, mget) { zval *z_ret; diff --git a/redis_cluster.h b/redis_cluster.h index 5ecd8204..74903e67 100644 --- a/redis_cluster.h +++ b/redis_cluster.h @@ -130,6 +130,7 @@ PHP_METHOD(RedisCluster, mset); PHP_METHOD(RedisCluster, msetnx); PHP_METHOD(RedisCluster, mset); PHP_METHOD(RedisCluster, del); +PHP_METHOD(RedisCluster, unlink); PHP_METHOD(RedisCluster, dump); PHP_METHOD(RedisCluster, setex); PHP_METHOD(RedisCluster, psetex); diff --git a/redis_commands.c b/redis_commands.c index ac0ed30d..e2db37c6 100644 --- a/redis_commands.c +++ b/redis_commands.c @@ -2949,6 +2949,14 @@ int redis_sdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, "SDIFFSTORE", sizeof("SDIFFSTORE")-1, 1, 0, cmd, cmd_len, slot); } +/* UNLINK */ +int redis_unlink_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + char **cmd, int *cmd_len, short *slot, void **ctx) +{ + return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, + "UNLINK", sizeof("UNLINK")-1, 1, 0, cmd, cmd_len, slot); +} + /* COMMAND */ int redis_command_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) diff --git a/redis_commands.h b/redis_commands.h index 51d0b9f1..b3175364 100644 --- a/redis_commands.h +++ b/redis_commands.h @@ -236,6 +236,9 @@ int redis_sdiff_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, int redis_sdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); +int redis_unlink_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + char **cmd, int *cmd_len, short *slot, void **ctx); + int redis_command_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); diff --git a/tests/RedisArrayTest.php b/tests/RedisArrayTest.php index 0233b6d2..01e1c2f2 100644 --- a/tests/RedisArrayTest.php +++ b/tests/RedisArrayTest.php @@ -18,8 +18,30 @@ function parseHostPort($str, &$host, &$port) { $port = substr($str, $pos+1); } +function getRedisVersion($obj_r) { + $arr_info = $obj_r->info(); + if (!$arr_info || !isset($arr_info['redis_version'])) { + return "0.0.0"; + } + return $arr_info['redis_version']; +} + +/* Determine the lowest redis version attached to this RedisArray object */ +function getMinVersion($obj_ra) { + $min_version = "0.0.0"; + foreach ($obj_ra->_hosts() as $host) { + $version = getRedisVersion($obj_ra->_instance($host)); + if (version_compare($version, $min_version) > 0) { + $min_version = $version; + } + } + + return $min_version; +} + class Redis_Array_Test extends TestSuite { + private $min_version; private $strings; public $ra = NULL; private $data = NULL; @@ -34,6 +56,7 @@ class Redis_Array_Test extends TestSuite global $newRing, $oldRing, $useIndex; $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex)); + $this->min_version = getMinVersion($this->ra); } public function testMSet() { @@ -141,6 +164,8 @@ class Redis_Rehashing_Test extends TestSuite public $ra = NULL; private $useIndex; + private $min_version; + // data private $strings; private $sets; @@ -185,6 +210,7 @@ class Redis_Rehashing_Test extends TestSuite // create array $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex)); + $this->min_version = getMinVersion($this->ra); } public function testFlush() { @@ -206,12 +232,12 @@ class Redis_Rehashing_Test extends TestSuite foreach($this->strings as $k => $v) { $this->ra->set($k, $v); } - + // sets foreach($this->sets as $k => $v) { call_user_func_array(array($this->ra, 'sadd'), array_merge(array($k), $v)); } - + // lists foreach($this->lists as $k => $v) { call_user_func_array(array($this->ra, 'rpush'), array_merge(array($k), $v)); @@ -221,7 +247,7 @@ class Redis_Rehashing_Test extends TestSuite foreach($this->hashes as $k => $v) { $this->ra->hmset($k, $v); } - + // sorted sets foreach($this->zsets as $k => $v) { call_user_func_array(array($this->ra, 'zadd'), array_merge(array($k), $v)); @@ -314,6 +340,7 @@ class Redis_Rehashing_Test extends TestSuite class Redis_Auto_Rehashing_Test extends TestSuite { public $ra = NULL; + private $min_version; // data private $strings; @@ -330,6 +357,7 @@ class Redis_Auto_Rehashing_Test extends TestSuite { // create array $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'autorehash' => TRUE)); + $this->min_version = getMinVersion($this->ra); } public function testDistribute() { @@ -378,11 +406,14 @@ class Redis_Auto_Rehashing_Test extends TestSuite { // Test node-specific multi/exec class Redis_Multi_Exec_Test extends TestSuite { public $ra = NULL; + private $min_version; public function setUp() { global $newRing, $oldRing, $useIndex; + // create array $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex)); + $this->min_version = getMinVersion($this->ra); } public function testInit() { @@ -464,6 +495,22 @@ class Redis_Multi_Exec_Test extends TestSuite { $this->assertEquals(0, $this->ra->exists('1_{employee:joe}_salary')); } + public function testMutliExecUnlink() { + if (version_compare($this->min_version, "4.0.0", "lt")) { + var_dump($this->min_version); + $this->markTestSkipped(); + } + + $this->ra->set('{unlink}:key1', 'bar'); + $this->ra->set('{unlink}:key2', 'bar'); + + $out = $this->ra->multi($this->ra->_target('{unlink}')) + ->del('{unlink}:key1', '{unlink}:key2') + ->exec(); + + $this->assertTrue($out[0] === 2); + } + public function testDiscard() { /* phpredis issue #87 */ $key = 'test_err'; @@ -502,11 +549,13 @@ class Redis_Multi_Exec_Test extends TestSuite { class Redis_Distributor_Test extends TestSuite { public $ra = NULL; + private $min_version; public function setUp() { global $newRing, $oldRing, $useIndex; // create array $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'distributor' => array($this, 'distribute'))); + $this->min_version = getMinVersion($this->ra); } public function testInit() { diff --git a/tests/RedisTest.php b/tests/RedisTest.php index f2ec3015..b7a0ba2b 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -456,7 +456,7 @@ class Redis_Test extends TestSuite $this->assertTrue($this->redis->ttl('key') ===7); $this->assertTrue($this->redis->get('key') === 'val'); } - + public function testPSetEx() { $this->redis->del('key'); $this->assertTrue($this->redis->psetex('key', 7 * 1000, 'val') === TRUE); @@ -626,38 +626,49 @@ class Redis_Test extends TestSuite $this->assertEquals(array(), $this->redis->keys(rand().rand().rand().'*')); } - public function testDelete() - { + protected function genericDelUnlink($cmd) { $key = 'key' . rand(); $this->redis->set($key, 'val'); $this->assertEquals('val', $this->redis->get($key)); - $this->assertEquals(1, $this->redis->del($key)); + $this->assertEquals(1, $this->redis->$cmd($key)); $this->assertEquals(false, $this->redis->get($key)); - // multiple, all existing - $this->redis->set('x', 0); - $this->redis->set('y', 1); - $this->redis->set('z', 2); - $this->assertEquals(3, $this->redis->del('x', 'y', 'z')); - $this->assertEquals(false, $this->redis->get('x')); - $this->assertEquals(false, $this->redis->get('y')); - $this->assertEquals(false, $this->redis->get('z')); - - // multiple, none existing - $this->assertEquals(0, $this->redis->del('x', 'y', 'z')); - $this->assertEquals(false, $this->redis->get('x')); - $this->assertEquals(false, $this->redis->get('y')); - $this->assertEquals(false, $this->redis->get('z')); - - // multiple, some existing - $this->redis->set('y', 1); - $this->assertEquals(1, $this->redis->del('x', 'y', 'z')); - $this->assertEquals(false, $this->redis->get('y')); - - $this->redis->set('x', 0); - $this->redis->set('y', 1); - $this->assertEquals(2, $this->redis->del(array('x', 'y'))); + // multiple, all existing + $this->redis->set('x', 0); + $this->redis->set('y', 1); + $this->redis->set('z', 2); + $this->assertEquals(3, $this->redis->$cmd('x', 'y', 'z')); + $this->assertEquals(false, $this->redis->get('x')); + $this->assertEquals(false, $this->redis->get('y')); + $this->assertEquals(false, $this->redis->get('z')); + + // multiple, none existing + $this->assertEquals(0, $this->redis->$cmd('x', 'y', 'z')); + $this->assertEquals(false, $this->redis->get('x')); + $this->assertEquals(false, $this->redis->get('y')); + $this->assertEquals(false, $this->redis->get('z')); + + // multiple, some existing + $this->redis->set('y', 1); + $this->assertEquals(1, $this->redis->$cmd('x', 'y', 'z')); + $this->assertEquals(false, $this->redis->get('y')); + + $this->redis->set('x', 0); + $this->redis->set('y', 1); + $this->assertEquals(2, $this->redis->$cmd(array('x', 'y'))); + } + + public function testDelete() { + $this->genericDelUnlink("DEL"); + } + + public function testUnlink() { + if (version_compare($this->version, "4.0.0", "lt")) { + $this->markTestSkipped(); + return; + } + $this->genericDelUnlink("UNLINK"); } public function testType() |