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

Skip to content

Commit 711f053

Browse files
Redis >= 2.6.12 extended set options
Implement the new SET options as per the redis documentation: http://redis.io/commands/set You can now pass the new options (ex=>sec, px=>milisec, xx, nx) as an array of options to the SET command and phpredis will handle them. If you pass key, value, <long> phpredis will still redirect to SETEX as it did before (to avoid breaking implementations). Addresses phpredis#364
1 parent 58f38ae commit 711f053

File tree

4 files changed

+217
-77
lines changed

4 files changed

+217
-77
lines changed

README.markdown

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -604,19 +604,30 @@ $redis->get('key');
604604

605605
### set
606606
-----
607-
_**Description**_: Set the string value in argument as value of the key.
607+
_**Description**_: Set the string value in argument as value of the key. If you're using Redis >= 2.6.12, you can pass extended options as explained below
608608

609609
##### *Parameters*
610610
*Key*
611611
*Value*
612-
*Timeout* (optional). Calling `SETEX` is preferred if you want a timeout.
612+
*Timeout or Options Array* (optional). If you pass an integer, phpredis will redirect to SETEX, and will try to use Redis >= 2.6.12 extended options if you pass an array with valid values
613613

614614
##### *Return value*
615615
*Bool* `TRUE` if the command is successful.
616616

617617
##### *Examples*
618618
~~~
619+
// Simple key -> value set
619620
$redis->set('key', 'value');
621+
622+
// Will redirect, and actually make an SETEX call
623+
$redis->set('key','value', 10);
624+
625+
// Will set the key, if it doesn't exist, with a ttl of 10 seconds
626+
$redis->set('key', 'value', Array('nx', 'ex'=>10);
627+
628+
// Will set a key, if it does exist, with a ttl of 1000 miliseconds
629+
$redis->set('key', 'value', Array('xx', 'px'=>1000);
630+
620631
~~~
621632

622633
### setex, psetex

common.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
#ifndef REDIS_COMMON_H
66
#define REDIS_COMMON_H
77

8+
/* NULL check so Eclipse doesn't go crazy */
9+
#ifndef NULL
10+
#define NULL ((void *) 0)
11+
#endif
12+
813
#define redis_sock_name "Redis Socket Buffer"
914
#define REDIS_SOCK_STATUS_FAILED 0
1015
#define REDIS_SOCK_STATUS_DISCONNECTED 1
@@ -138,6 +143,15 @@ else if(redis_sock->mode == MULTI) { \
138143

139144
#define REDIS_PROCESS_RESPONSE(function) REDIS_PROCESS_RESPONSE_CLOSURE(function, NULL)
140145

146+
/* Extended SET argument detection */
147+
#define IS_EX_ARG(a) ((a[0]=='e' || a[0]=='E') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
148+
#define IS_PX_ARG(a) ((a[0]=='p' || a[0]=='P') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
149+
#define IS_NX_ARG(a) ((a[0]=='n' || a[0]=='N') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
150+
#define IS_XX_ARG(a) ((a[0]=='x' || a[0]=='X') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
151+
152+
#define IS_EX_PX_ARG(a) (IS_EX_ARG(a) || IS_PX_ARG(a))
153+
#define IS_NX_XX_ARG(a) (IS_NX_ARG(a) || IS_XX_ARG(a))
154+
141155
typedef enum {ATOMIC, MULTI, PIPELINE} redis_mode;
142156

143157
typedef struct fold_item {

redis.c

Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -802,43 +802,103 @@ PHP_METHOD(Redis, close)
802802
}
803803
/* }}} */
804804

805-
/* {{{ proto boolean Redis::set(string key, mixed value)
806-
*/
807-
PHP_METHOD(Redis, set)
808-
{
805+
/* {{{ proto boolean Redis::set(string key, mixed value, long timeout | array options) */
806+
PHP_METHOD(Redis, set) {
809807
zval *object;
810808
RedisSock *redis_sock;
811-
char *key = NULL, *val = NULL, *cmd;
809+
char *key = NULL, *val = NULL, *cmd, *exp_type = NULL, *set_type = NULL;
812810
int key_len, val_len, cmd_len;
813811
long expire = -1;
814812
int val_free = 0, key_free = 0;
815-
zval *z_value;
813+
zval *z_value, *z_opts = NULL;
816814

817-
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz|l",
818-
&object, redis_ce, &key, &key_len,
819-
&z_value, &expire) == FAILURE) {
815+
// Make sure the arguments are correct
816+
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz|z",
817+
&object, redis_ce, &key, &key_len, &z_value,
818+
&z_opts) == FAILURE)
819+
{
820820
RETURN_FALSE;
821821
}
822822

823-
if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) {
823+
// Ensure we can grab our redis socket
824+
if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) {
824825
RETURN_FALSE;
825826
}
826827

828+
/* Our optional argument can either be a long (to support legacy SETEX */
829+
/* redirection), or an array with Redis >= 2.6.12 set options */
830+
if(z_opts && Z_TYPE_P(z_opts) != IS_LONG && Z_TYPE_P(z_opts) != IS_ARRAY) {
831+
RETURN_FALSE;
832+
}
833+
834+
/* Serialization, key prefixing */
827835
val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC);
828-
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
829-
if(expire > 0) {
830-
cmd_len = redis_cmd_format_static(&cmd, "SETEX", "sds", key, key_len, expire, val, val_len);
836+
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
837+
838+
if(z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) {
839+
HashTable *kt = Z_ARRVAL_P(z_opts);
840+
int type;
841+
unsigned int ht_key_len;
842+
unsigned long idx;
843+
char *k;
844+
zval **v;
845+
846+
/* Iterate our option array */
847+
for(zend_hash_internal_pointer_reset(kt);
848+
zend_hash_has_more_elements(kt) == SUCCESS;
849+
zend_hash_move_forward(kt))
850+
{
851+
// Grab key and value
852+
type = zend_hash_get_current_key_ex(kt, &k, &ht_key_len, &idx, 0, NULL);
853+
zend_hash_get_current_data(kt, (void**)&v);
854+
855+
if(type == HASH_KEY_IS_STRING && (Z_TYPE_PP(v) == IS_LONG) &&
856+
(Z_LVAL_PP(v) > 0) && IS_EX_PX_ARG(k))
857+
{
858+
exp_type = k;
859+
expire = Z_LVAL_PP(v);
860+
} else if(Z_TYPE_PP(v) == IS_STRING && IS_NX_XX_ARG(Z_STRVAL_PP(v))) {
861+
set_type = Z_STRVAL_PP(v);
862+
}
863+
}
864+
} else if(z_opts && Z_TYPE_P(z_opts) == IS_LONG) {
865+
expire = Z_LVAL_P(z_opts);
866+
}
867+
868+
/* Now let's construct the command we want */
869+
if(exp_type && set_type) {
870+
/* SET <key> <value> NX|XX PX|EX <timeout> */
871+
cmd_len = redis_cmd_format_static(&cmd, "SET", "ssssl", key, key_len,
872+
val, val_len, set_type, 2, exp_type,
873+
2, expire);
874+
} else if(exp_type) {
875+
/* SET <key> <value> PX|EX <timeout> */
876+
cmd_len = redis_cmd_format_static(&cmd, "SET", "sssl", key, key_len,
877+
val, val_len, exp_type, 2, expire);
878+
} else if(set_type) {
879+
/* SET <key> <value> NX|XX */
880+
cmd_len = redis_cmd_format_static(&cmd, "SET", "sss", key, key_len,
881+
val, val_len, set_type, 2);
882+
} else if(expire > 0) {
883+
/* Backward compatible SETEX redirection */
884+
cmd_len = redis_cmd_format_static(&cmd, "SETEX", "sds", key, key_len,
885+
expire, val, val_len);
831886
} else {
832-
cmd_len = redis_cmd_format_static(&cmd, "SET", "ss", key, key_len, val, val_len);
887+
/* SET <key> <value> */
888+
cmd_len = redis_cmd_format_static(&cmd, "SET", "ss", key, key_len,
889+
val, val_len);
833890
}
834-
if(val_free) efree(val);
891+
892+
/* Free our key or value if we prefixed/serialized */
835893
if(key_free) efree(key);
894+
if(val_free) efree(val);
836895

837-
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
838-
IF_ATOMIC() {
839-
redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
840-
}
841-
REDIS_PROCESS_RESPONSE(redis_boolean_response);
896+
/* Kick off the command */
897+
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
898+
IF_ATOMIC() {
899+
redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
900+
}
901+
REDIS_PROCESS_RESPONSE(redis_boolean_response);
842902
}
843903

844904
PHPAPI void redis_generic_setex(INTERNAL_FUNCTION_PARAMETERS, char *keyword) {
@@ -2360,7 +2420,6 @@ PHP_METHOD(Redis, sMembers)
23602420
}
23612421
/* }}} */
23622422

2363-
23642423
PHPAPI int generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len,
23652424
int min_argc, RedisSock **out_sock, int has_timeout, int all_keys, int can_serialize)
23662425
{

tests/TestRedis.php

Lines changed: 110 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -138,84 +138,140 @@ public function testErr() {
138138

139139
public function testSet()
140140
{
141-
$this->assertEquals(TRUE, $this->redis->set('key', 'nil'));
142-
$this->assertEquals('nil', $this->redis->get('key'));
141+
$this->assertEquals(TRUE, $this->redis->set('key', 'nil'));
142+
$this->assertEquals('nil', $this->redis->get('key'));
143143

144-
$this->assertEquals(TRUE, $this->redis->set('key', 'val'));
144+
$this->assertEquals(TRUE, $this->redis->set('key', 'val'));
145145

146-
$this->assertEquals('val', $this->redis->get('key'));
147-
$this->assertEquals('val', $this->redis->get('key'));
148-
$this->redis->delete('keyNotExist');
149-
$this->assertEquals(FALSE, $this->redis->get('keyNotExist'));
146+
$this->assertEquals('val', $this->redis->get('key'));
147+
$this->assertEquals('val', $this->redis->get('key'));
148+
$this->redis->delete('keyNotExist');
149+
$this->assertEquals(FALSE, $this->redis->get('keyNotExist'));
150150

151-
$this->redis->set('key2', 'val');
152-
$this->assertEquals('val', $this->redis->get('key2'));
151+
$this->redis->set('key2', 'val');
152+
$this->assertEquals('val', $this->redis->get('key2'));
153153

154-
$value = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
155-
$this->redis->set('key2', $value);
156-
$this->assertEquals($value, $this->redis->get('key2'));
157-
$this->assertEquals($value, $this->redis->get('key2'));
154+
$value = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
155+
156+
$this->redis->set('key2', $value);
157+
$this->assertEquals($value, $this->redis->get('key2'));
158+
$this->assertEquals($value, $this->redis->get('key2'));
158159

159-
$this->redis->delete('key');
160-
$this->redis->delete('key2');
160+
$this->redis->delete('key');
161+
$this->redis->delete('key2');
161162

162163

163-
$i = 66000;
164-
$value2 = 'X';
165-
while($i--) {
166-
$value2 .= 'A';
167-
}
168-
$value2 .= 'X';
164+
$i = 66000;
165+
$value2 = 'X';
166+
while($i--) {
167+
$value2 .= 'A';
168+
}
169+
$value2 .= 'X';
169170

170-
$this->redis->set('key', $value2);
171+
$this->redis->set('key', $value2);
171172
$this->assertEquals($value2, $this->redis->get('key'));
172-
$this->redis->delete('key');
173-
$this->assertEquals(False, $this->redis->get('key'));
173+
$this->redis->delete('key');
174+
$this->assertEquals(False, $this->redis->get('key'));
174175

175-
$data = gzcompress('42');
176+
$data = gzcompress('42');
176177
$this->assertEquals(True, $this->redis->set('key', $data));
177-
$this->assertEquals('42', gzuncompress($this->redis->get('key')));
178+
$this->assertEquals('42', gzuncompress($this->redis->get('key')));
178179

179-
$this->redis->delete('key');
180-
$data = gzcompress('value1');
180+
$this->redis->delete('key');
181+
$data = gzcompress('value1');
181182
$this->assertEquals(True, $this->redis->set('key', $data));
182-
$this->assertEquals('value1', gzuncompress($this->redis->get('key')));
183-
184-
$this->redis->delete('key');
185-
$this->assertEquals(TRUE, $this->redis->set('key', 0));
186-
$this->assertEquals('0', $this->redis->get('key'));
187-
$this->assertEquals(TRUE, $this->redis->set('key', 1));
188-
$this->assertEquals('1', $this->redis->get('key'));
189-
$this->assertEquals(TRUE, $this->redis->set('key', 0.1));
190-
$this->assertEquals('0.1', $this->redis->get('key'));
191-
$this->assertEquals(TRUE, $this->redis->set('key', '0.1'));
192-
$this->assertEquals('0.1', $this->redis->get('key'));
193-
$this->assertEquals(TRUE, $this->redis->set('key', TRUE));
194-
$this->assertEquals('1', $this->redis->get('key'));
195-
196-
$this->assertEquals(True, $this->redis->set('key', ''));
197-
$this->assertEquals('', $this->redis->get('key'));
198-
$this->assertEquals(True, $this->redis->set('key', NULL));
199-
$this->assertEquals('', $this->redis->get('key'));
183+
$this->assertEquals('value1', gzuncompress($this->redis->get('key')));
184+
185+
$this->redis->delete('key');
186+
$this->assertEquals(TRUE, $this->redis->set('key', 0));
187+
$this->assertEquals('0', $this->redis->get('key'));
188+
$this->assertEquals(TRUE, $this->redis->set('key', 1));
189+
$this->assertEquals('1', $this->redis->get('key'));
190+
$this->assertEquals(TRUE, $this->redis->set('key', 0.1));
191+
$this->assertEquals('0.1', $this->redis->get('key'));
192+
$this->assertEquals(TRUE, $this->redis->set('key', '0.1'));
193+
$this->assertEquals('0.1', $this->redis->get('key'));
194+
$this->assertEquals(TRUE, $this->redis->set('key', TRUE));
195+
$this->assertEquals('1', $this->redis->get('key'));
196+
197+
$this->assertEquals(True, $this->redis->set('key', ''));
198+
$this->assertEquals('', $this->redis->get('key'));
199+
$this->assertEquals(True, $this->redis->set('key', NULL));
200+
$this->assertEquals('', $this->redis->get('key'));
200201

201202
$this->assertEquals(True, $this->redis->set('key', gzcompress('42')));
202203
$this->assertEquals('42', gzuncompress($this->redis->get('key')));
204+
}
205+
206+
/* Extended SET options for Redis >= 2.6.12 */
207+
public function testExtendedSet() {
208+
// Skip the test if we don't have a new enough version of Redis
209+
if(version_compare($this->version, '2.6.12', 'lt')) {
210+
$this->markTestSkipped();
211+
return;
212+
}
203213

214+
/* Legacy SETEX redirection */
215+
$this->redis->del('foo');
216+
$this->assertTrue($this->redis->set('foo','bar', 20));
217+
$this->assertEquals($this->redis->get('foo'), 'bar');
218+
$this->assertEquals($this->redis->ttl('foo'), 20);
219+
220+
/* Invalid third arguments */
221+
$this->assertFalse($this->redis->set('foo','bar','baz'));
222+
$this->assertFalse($this->redis->set('foo','bar',new StdClass()));
223+
224+
/* Set if not exist */
225+
$this->redis->del('foo');
226+
$this->assertTrue($this->redis->set('foo','bar',Array('nx')));
227+
$this->assertEquals($this->redis->get('foo'), 'bar');
228+
$this->assertFalse($this->redis->set('foo','bar',Array('nx')));
229+
230+
/* Set if exists */
231+
$this->assertTrue($this->redis->set('foo','bar',Array('xx')));
232+
$this->assertEquals($this->redis->get('foo'), 'bar');
233+
$this->redis->del('foo');
234+
$this->assertFalse($this->redis->set('foo','bar',Array('xx')));
235+
236+
/* Set with a TTL */
237+
$this->assertTrue($this->redis->set('foo','bar',Array('ex'=>100)));
238+
$this->assertEquals($this->redis->ttl('foo'), 100);
239+
240+
/* Set with a PTTL */
241+
$this->assertTrue($this->redis->set('foo','bar',Array('px'=>100000)));
242+
$this->assertTrue(100000 - $this->redis->pttl('foo') < 1000);
243+
244+
/* Set if exists, with a TTL */
245+
$this->assertTrue($this->redis->set('foo','bar',Array('xx','ex'=>105)));
246+
$this->assertEquals($this->redis->ttl('foo'), 105);
247+
$this->assertEquals($this->redis->get('foo'), 'bar');
248+
249+
/* Set if not exists, with a TTL */
250+
$this->redis->del('foo');
251+
$this->assertTrue($this->redis->set('foo','bar', Array('nx', 'ex'=>110)));
252+
$this->assertEquals($this->redis->ttl('foo'), 110);
253+
$this->assertEquals($this->redis->get('foo'), 'bar');
254+
$this->assertFalse($this->redis->set('foo','bar', Array('nx', 'ex'=>110)));
255+
256+
/* Throw some nonsense into the array, and check that the TTL came through */
257+
$this->redis->del('foo');
258+
$this->assertTrue($this->redis->set('foo','barbaz', Array('not-valid','nx','invalid','ex'=>200)));
259+
$this->assertEquals($this->redis->ttl('foo'), 200);
260+
$this->assertEquals($this->redis->get('foo'), 'barbaz');
204261
}
205-
public function testGetSet() {
206262

207-
$this->redis->delete('key');
208-
$this->assertTrue($this->redis->getSet('key', '42') === FALSE);
209-
$this->assertTrue($this->redis->getSet('key', '123') === '42');
210-
$this->assertTrue($this->redis->getSet('key', '123') === '123');
263+
public function testGetSet() {
264+
$this->redis->delete('key');
265+
$this->assertTrue($this->redis->getSet('key', '42') === FALSE);
266+
$this->assertTrue($this->redis->getSet('key', '123') === '42');
267+
$this->assertTrue($this->redis->getSet('key', '123') === '123');
211268
}
212269

213270
public function testRandomKey() {
214-
215271
for($i = 0; $i < 1000; $i++) {
216272
$k = $this->redis->randomKey();
217-
$this->assertTrue($this->redis->exists($k));
218-
}
273+
$this->assertTrue($this->redis->exists($k));
274+
}
219275
}
220276

221277
public function testRename() {

0 commit comments

Comments
 (0)