8000 Introduce `Redis::OPT_PACK_IGNORE_NUMBERS` option. · phpredis/phpredis@f9ce942 · GitHub
[go: up one dir, main page]

Skip to content

Commit f9ce942

Browse files
Introduce Redis::OPT_PACK_IGNORE_NUMBERS option.
Adds an option that instructs PhpRedis to not serialize or compress numeric values. Specifically where `Z_TYPE_P(z) == IS_LONG` or `Z_TYPE_P(z) == IS_DOUBLE`. This flag lets the user enable serialization and/or compression while still using the various increment/decrement command (`INCR`, `INCRBY`, `DECR`, `DECRBY`, `INCRBYFLOAT`, `HINCRBY`, and `HINCRBYFLOAT`). Because PhpRedis can't be certain that this option was enabled when writing keys, there is a small runtime cost on the read-side that tests whether or not the value its reading is a pure integer or floating point value. See #23
1 parent 41e1141 commit f9ce942

7 files changed

+189
-29
lines changed

common.h

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -91,20 +91,21 @@ typedef enum _PUBSUB_TYPE {
9191
#define REDIS_SUBS_BUCKETS 3
9292

9393
/* options */
94-
#define REDIS_OPT_SERIALIZER 1
95-
#define REDIS_OPT_PREFIX 2
96-
#define REDIS_OPT_READ_TIMEOUT 3
97-
#define REDIS_OPT_SCAN 4
98-
#define REDIS_OPT_FAILOVER 5
99-
#define REDIS_OPT_TCP_KEEPALIVE 6
100-
#define REDIS_OPT_COMPRESSION 7
101-
#define REDIS_OPT_REPLY_LITERAL 8
102-
#define REDIS_OPT_COMPRESSION_LEVEL 9
103-
#define REDIS_OPT_NULL_MBULK_AS_NULL 10
104-
#define REDIS_OPT_MAX_RETRIES 11
105-
#define REDIS_OPT_BACKOFF_ALGORITHM 12
106-
#define REDIS_OPT_BACKOFF_BASE 13
107-
#define REDIS_OPT_BACKOFF_CAP 14
94+
#define REDIS_OPT_SERIALIZER 1
95+
#define REDIS_OPT_PREFIX 2
96+
#define REDIS_OPT_READ_TIMEOUT 3
97+
#define REDIS_OPT_SCAN 4
98+
#define REDIS_OPT_FAILOVER 5
99+
#define REDIS_OPT_TCP_KEEPALIVE 6
100+
#define REDIS_OPT_COMPRESSION 7
101+
#define REDIS_OPT_REPLY_LITERAL 8
102+
#define REDIS_OPT_COMPRESSION_LEVEL 9
103+
#define REDIS_OPT_NULL_MBULK_AS_NULL 10
104+
#define REDIS_OPT_MAX_RETRIES 11
105+
#define REDIS_OPT_BACKOFF_ALGORITHM 12
106+
#define REDIS_OPT_BACKOFF_BASE 13
107+
#define REDIS_OPT_BACKOFF_CAP 14
108+
#define REDIS_OPT_PACK_IGNORE_NUMBERS 15
108109

109110
/* cluster options */
110111
#define REDIS_FAILOVER_NONE 0
@@ -300,6 +301,7 @@ typedef struct {
300301
zend_string *persistent_id;
301302
HashTable *subs[REDIS_SUBS_BUCKETS];
302303
redis_serializer serializer;
304+
zend_bool pack_ignore_numbers;
303305
int compression;
304306
int compression_level;
305307
long dbNumber;

library.c

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3831,12 +3831,38 @@ redis_uncompress(RedisSock *redis_sock, char **dst, size_t *dstlen, const char *
38313831
return 0;
38323832
}
38333833

3834+
static int serialize_generic_zval(char **dst, size_t *len, zval *zsrc) {
3835+
zend_string *zstr;
3836+
3837+
zstr = zval_get_string_func(zsrc);
3838+
if (ZSTR_IS_INTERNED(zstr)) {
3839+
*dst = ZSTR_VAL(zstr);
3840+
*len = ZSTR_LEN(zstr);
3841+
return 0;
3842+
}
3843+
3844+
*dst = estrndup(ZSTR_VAL(zstr), ZSTR_LEN(zstr));
3845+
*len = ZSTR_LEN(zstr);
3846+
3847+
zend_string_release(zstr);
3848+
3849+
return 1;
3850+
}
3851+
3852+
38343853
PHP_REDIS_API int
38353854
redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) {
38363855
size_t tmplen;
38373856
int tmpfree;
38383857
char *tmp;
38393858

3859+
/* Don't pack actual numbers if the user asked us not to */
3860+
if (UNEXPECTED(redis_sock->pack_ignore_numbers &&
3861+
(Z_TYPE_P(z) == IS_LONG || Z_TYPE_P(z) == IS_DOUBLE)))
3862+
{
3863+
return serialize_generic_zval(val, val_len, z);
3864+
}
3865+
38403866
/* First serialize */
38413867
tmpfree = redis_serialize(redis_sock, z, &tmp, &tmplen);
38423868

@@ -3851,9 +3877,29 @@ redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) {
38513877

38523878
PHP_REDIS_API int
38533879
redis_unpack(RedisSock *redis_sock, const char *src, int srclen, zval *zdst) {
3880+
zend_long lval;
3881+
double dval;
38543882
size_t len;
38553883
char *buf;
38563884

3885+
if (UNEXPECTED((redis_sock->serializer != REDIS_SERIALIZER_NONE &&
3886+
redis_sock->compression != REDIS_COMPRESSION_NONE) &&
3887+
redis_sock->pack_ignore_numbers) &&
3888+
srclen > 0 && srclen < 24)
3889+
{
3890+
switch (is_numeric_string(src, srclen, &lval, &dval, 0)) {
3891+
case IS_LONG:
3892+
ZVAL_LONG(zdst, lval);
3893+
return 1;
3894+
case IS_DOUBLE:
3895+
ZVAL_DOUBLE(zdst, dval);
3896+
return 1;
3897+
default:
3898+
/* Fallthrough */
3899+
break;
3900+
}
3901+
}
3902+
38573903
/* Uncompress, then unserialize */
38583904
if (redis_uncompress(redis_sock, &buf, &len, src, srclen)) {
38593905
if (!redis_unserialize(redis_sock, buf, len, zdst)) {
@@ -3898,18 +3944,8 @@ redis_serialize(RedisSock *redis_sock, zval *z, char **val, size_t *val_len)
38983944
*val_len = 5;
38993945
break;
39003946

3901-
default: { /* copy */
3902-
zend_string *zstr = zval_get_string_func(z);
3903-
if (ZSTR_IS_INTERNED(zstr)) { // do not reallocate interned strings
3904-
*val = ZSTR_VAL(zstr);
3905-
*val_len = ZSTR_LEN(zstr);
3906-
return 0;
3907-
}
3908-
*val = estrndup(ZSTR_VAL(zstr), ZSTR_LEN(zstr));
3909-
*val_len = ZSTR_LEN(zstr);
3910-
zend_string_efree(zstr);
3911-
return 1;
3912-
}
3947+
default:
3948+
return serialize_generic_zval(val, val_len, z);
39133949
}
39143950
break;
39153951
case REDIS_SERIALIZER_PHP:

redis.stub.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,13 @@ class Redis {
151151
*/
152152
public const OPT_NULL_MULTIBULK_AS_NULL = UNKNOWN;
153153

154+
/**
155+
* @var int
156+
* @cvalue REDIS_OPT_PACK_IGNORE_NUMBERS
157+
*
158+
*/
159+
public const OPT_PACK_IGNORE_NUMBERS = UNKNOWN;
160+
154161
/**
155162
*
156163
* @var int

redis_arginfo.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 1f8f22ab9cd1635066463b20ab12d295c11b4ac7 */
2+
* Stub hash: 78283cf59cefb411c09adf7a0f0bd234c65327b3 */
33

44
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0)
55
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null")
@@ -1817,6 +1817,12 @@ static zend_class_entry *register_class_Redis(void)
18171817
zend_declare_class_constant_ex(class_entry, const_OPT_NULL_MULTIBULK_AS_NULL_name, &const_OPT_NULL_MULTIBULK_AS_NULL_value, ZEND_ACC_PUBLIC, NULL);
18181818
zend_string_release(const_OPT_NULL_MULTIBULK_AS_NULL_name);
18191819

1820+
zval const_OPT_PACK_IGNORE_NUMBERS_value;
1821+
ZVAL_LONG(&const_OPT_PACK_IGNORE_NUMBERS_value, REDIS_OPT_PACK_IGNORE_NUMBERS);
1822+
zend_string *const_OPT_PACK_IGNORE_NUMBERS_name = zend_string_init_interned("OPT_PACK_IGNORE_NUMBERS", sizeof("OPT_PACK_IGNORE_NUMBERS") - 1, 1);
1823+
zend_declare_class_constant_ex(class_entry, const_OPT_PACK_IGNORE_NUMBERS_name, &const_OPT_PACK_IGNORE_NUMBERS_value, ZEND_ACC_PUBLIC, NULL);
1824+
zend_string_release(const_OPT_PACK_IGNORE_NUMBERS_name);
1825+
18201826
zval const_SERIALIZER_NONE_value;
18211827
ZVAL_LONG(&const_SERIALIZER_NONE_value, REDIS_SERIALIZER_NONE);
18221828
zend_string *const_SERIALIZER_NONE_name = zend_string_init_interned("SERIALIZER_NONE", sizeof("SERIALIZER_NONE") - 1, 1);

redis_commands.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6147,6 +6147,8 @@ void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS,
61476147
RETURN_LONG(redis_sock->compression);
61486148
case REDIS_OPT_COMPRESSION_LEVEL:
61496149
RETURN_LONG(redis_sock->compression_level);
6150+
case REDIS_OPT_PACK_IGNORE_NUMBERS:
6151+
RETURN_BOOL(redis_sock->pack_ignore_numbers);
61506152
case REDIS_OPT_PREFIX:
61516153
if (redis_sock->prefix) {
61526154
RETURN_STRINGL(ZSTR_VAL(redis_sock->prefix), ZSTR_LEN(redis_sock->prefix));
@@ -6235,6 +6237,9 @@ void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS,
62356237
RETURN_TRUE;
62366238
}
62376239
break;
6240+
case REDIS_OPT_PACK_IGNORE_NUMBERS:
6241+
redis_sock->pack_ignore_numbers = zval_is_true(val);
6242+
RETURN_TRUE;
62386243
case REDIS_OPT_COMPRESSION_LEVEL:
62396244
val_long = zval_get_long(val);
62406245
redis_sock->compression_level = val_long;

redis_legacy_arginfo.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 1f8f22ab9cd1635066463b20ab12d295c11b4ac7 */
2+
* Stub hash: 78283cf59cefb411c09adf7a0f0bd234c65327b3 */
33

44
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 179B 0, 0, 0)
55
ZEND_ARG_INFO(0, options)
@@ -1660,6 +1660,12 @@ static zend_class_entry *register_class_Redis(void)
16601660
zend_declare_class_constant_ex(class_entry, const_OPT_NULL_MULTIBULK_AS_NULL_name, &const_OPT_NULL_MULTIBULK_AS_NULL_value, ZEND_ACC_PUBLIC, NULL);
16611661
zend_string_release(const_OPT_NULL_MULTIBULK_AS_NULL_name);
16621662

1663+
zval const_OPT_PACK_IGNORE_NUMBERS_value;
1664+
ZVAL_LONG(&const_OPT_PACK_IGNORE_NUMBERS_value, REDIS_OPT_PACK_IGNORE_NUMBERS);
1665+
zend_string *const_OPT_PACK_IGNORE_NUMBERS_name = zend_string_init_interned("OPT_PACK_IGNORE_NUMBERS", sizeof("OPT_PACK_IGNORE_NUMBERS") - 1, 1);
1666+
zend_declare_class_constant_ex(class_entry, const_OPT_PACK_IGNORE_NUMBERS_name, &const_OPT_PACK_IGNORE_NUMBERS_value, ZEND_ACC_PUBLIC, NULL);
1667+
zend_string_release(const_OPT_PACK_IGNORE_NUMBERS_name);
1668+
16631669
zval const_SERIALIZER_NONE_value;
16641670
ZVAL_LONG(&const_SERIALIZER_NONE_value, REDIS_SERIALIZER_NONE);
16651671
zend_string *const_SERIALIZER_NONE_name = zend_string_init_interned("SERIALIZER_NONE", sizeof("SERIALIZER_NONE") - 1, 1);

tests/RedisTest.php

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public function setUp() {
7676
$info = $this->redis->info();
7777
$this->version = (isset($info['redis_version'])?$info['redis_version']:'0.0.0');
7878
$this->is_keydb = $this->detectKeyDB($info);
79-
$this->is_valkey = $this->detectValKey($info);
79+
$this->is_valkey = $this->detectValKey($info);
8080
}
8181

8282
protected function minVersionCheck($version) {
@@ -4958,6 +4958,104 @@ public function testSerializerPHP() {
49584958
$this->redis->setOption(Redis::OPT_PREFIX, '');
49594959
}
49604960

4961+
private function cartesianProduct(array $arrays) {
4962+
$result = [[]];
4963+
4964+
foreach ($arrays as $array) {
4965+
$append = [];
4966+
foreach ($result as $product) {
4967+
foreach ($array as $item) {
4968+
$newProduct = $product;
4969+
$newProduct[] = $item;
4970+
$append[] = $newProduct;
4971+
}
4972+
}
4973+
4974+
$result = $append;
4975+
}
4976+
4977+
return $result;
4978+
}
4979+
4980+
public function testIgnoreNumbers() {
4981+
$combinations = $this->cartesianProduct([
4982+
[false, true, false],
4983+
$this->getSerializers(),
4984+
$this->getCompressors(),
4985+
]);
4986+
4987+
foreach ($combinations as [$ignore, $serializer, $compression]) {
4988+
$this->redis->setOption(Redis::OPT_PACK_IGNORE_NUMBERS, $ignore);
4989+
$this->redis->setOption(Redis::OPT_SERIALIZER, $serializer);
4990+
$this->redis->setOption(Redis::OPT_COMPRESSION, $compression);
4991+
4992+
$this->assertIsInt($this->redis->del('answer'));
4993+
$this->assertIsInt($this->redis->del('hash'));
4994+
4995+
$transparent = $compression === Redis::COMPRESSION_NONE &&
4996+
($serializer === Redis::SERIALIZER_NONE ||
4997+
$serializer === Redis::SERIALIZER_JSON);
4998+
4999+
if ($transparent || $ignore) {
5000+
$expected_answer = 42;
5001+
$expected_pi = 3.14;
5002+
} else {
5003+
$expected_answer = false;
5004+
$expected_pi = false;
5005+
}
5006+
5007+
$this->assertTrue($this->redis->set('answer', 32));
5008+
$this->assertEquals($expected_answer, $this->redis->incr('answer', 10));
5009+
5010+
$this->assertTrue($this->redis->set('pi', 3.04));
5011+
$this->assertEquals($expected_pi, $this->redis->incrByFloat('pi', 0.1));
5012+
5013+
$this->assertEquals(1, $this->redis->hset('hash', 'answer', 32));
5014+
$this->assertEquals($expected_answer, $this->redis->hIncrBy('hash', 'answer', 10));
5015+
5016+
$this->assertEquals(1, $this->redis->hset('hash', 'pi', 3.04));
5017+
$this->assertEquals($expected_pi, $this->redis->hIncrByFloat('hash', 'pi', 0.1));
5018+
}
5019+
5020+
$this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE);
5021+
$this->redis->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_NONE);
5022+
$this->redis->setOption(Redis::OPT_PACK_IGNORE_NUMBERS, false);
5023+
}
5024+
5025+
function testIgnoreNumbersReturnTypes() {
5026+
$combinations = $this->cartesianProduct([
5027+
[false, true],
5028+
array_filter($this->getSerializers(), function($s) {
5029+
return $s !== Redis::SERIALIZER_NONE;
5030+
}),
5031+
array_filter($this->getCompressors(), function($c) {
5032+
return $c !== Redis::COMPRESSION_NONE;
5033+
}),
5034+
]);
5035+
5036+
foreach ($combinations as [$ignore, $serializer, $compression]) {
5037+
$this->redis->setOption(Redis::OPT_PACK_IGNORE_NUMBERS, $ignore);
5038+
$this->redis->setOption(Redis::OPT_SERIALIZER, $serializer);
5039+
$this->redis->setOption(Redis::OPT_COMPRESSION, $compression);
5040+
5041+
foreach ([42, 3.14] as $value) {
5042+
$this->assertTrue($this->redis->set('key', $value));
5043+
5044+
/* There's a known issue in the PHP JSON parser, which
5045+
can stringify numbers. Unclear the root cause */
5046+
if ($serializer == Redis::SERIALIZER_JSON) {
5047+
$this->assertEqualsWeak($value, $this->redis->get('key'));
5048+
} else {
5049+
$this->assertEquals($value, $this->redis->get('key'));
5050+
}
5051+
}
5052+
}
5053+
5054+
$this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE);
5055+
$this->redis->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_NONE);
5056+
$this->redis->setOption(Redis::OPT_PACK_IGNORE_NUMBERS, false);
5057+
}
5058+
49615059
public function testSerializerIGBinary() {
49625060
if ( ! defined('Redis::SERIALIZER_IGBINARY'))
49635061
$this->markTestSkipped('Redis::SERIALIZER_IGBINARY is not defined');

0 commit comments

Comments
 (0)
0