mirror of
https://github.com/php-win-ext/phpredis.git
synced 2026-03-24 00:52:16 +01:00
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
This commit is contained in:
committed by
Michael Grunder
parent
41e114177a
commit
f9ce9429ef
30
common.h
30
common.h
@@ -91,20 +91,21 @@ typedef enum _PUBSUB_TYPE {
|
||||
#define REDIS_SUBS_BUCKETS 3
|
||||
|
||||
/* options */
|
||||
#define REDIS_OPT_SERIALIZER 1
|
||||
#define REDIS_OPT_PREFIX 2
|
||||
#define REDIS_OPT_READ_TIMEOUT 3
|
||||
#define REDIS_OPT_SCAN 4
|
||||
#define REDIS_OPT_FAILOVER 5
|
||||
#define REDIS_OPT_TCP_KEEPALIVE 6
|
||||
#define REDIS_OPT_COMPRESSION 7
|
||||
#define REDIS_OPT_REPLY_LITERAL 8
|
||||
#define REDIS_OPT_COMPRESSION_LEVEL 9
|
||||
#define REDIS_OPT_NULL_MBULK_AS_NULL 10
|
||||
#define REDIS_OPT_MAX_RETRIES 11
|
||||
#define REDIS_OPT_BACKOFF_ALGORITHM 12
|
||||
#define REDIS_OPT_BACKOFF_BASE 13
|
||||
#define REDIS_OPT_BACKOFF_CAP 14
|
||||
#define REDIS_OPT_SERIALIZER 1
|
||||
#define REDIS_OPT_PREFIX 2
|
||||
#define REDIS_OPT_READ_TIMEOUT 3
|
||||
#define REDIS_OPT_SCAN 4
|
||||
#define REDIS_OPT_FAILOVER 5
|
||||
#define REDIS_OPT_TCP_KEEPALIVE 6
|
||||
#define REDIS_OPT_COMPRESSION 7
|
||||
#define REDIS_OPT_REPLY_LITERAL 8
|
||||
#define REDIS_OPT_COMPRESSION_LEVEL 9
|
||||
#define REDIS_OPT_NULL_MBULK_AS_NULL 10
|
||||
#define REDIS_OPT_MAX_RETRIES 11
|
||||
#define REDIS_OPT_BACKOFF_ALGORITHM 12
|
||||
#define REDIS_OPT_BACKOFF_BASE 13
|
||||
#define REDIS_OPT_BACKOFF_CAP 14
|
||||
#define REDIS_OPT_PACK_IGNORE_NUMBERS 15
|
||||
|
||||
/* cluster options */
|
||||
#define REDIS_FAILOVER_NONE 0
|
||||
@@ -300,6 +301,7 @@ typedef struct {
|
||||
zend_string *persistent_id;
|
||||
HashTable *subs[REDIS_SUBS_BUCKETS];
|
||||
redis_serializer serializer;
|
||||
zend_bool pack_ignore_numbers;
|
||||
int compression;
|
||||
int compression_level;
|
||||
long dbNumber;
|
||||
|
||||
60
library.c
60
library.c
@@ -3831,12 +3831,38 @@ redis_uncompress(RedisSock *redis_sock, char **dst, size_t *dstlen, const char *
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int serialize_generic_zval(char **dst, size_t *len, zval *zsrc) {
|
||||
zend_string *zstr;
|
||||
|
||||
zstr = zval_get_string_func(zsrc);
|
||||
if (ZSTR_IS_INTERNED(zstr)) {
|
||||
*dst = ZSTR_VAL(zstr);
|
||||
*len = ZSTR_LEN(zstr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*dst = estrndup(ZSTR_VAL(zstr), ZSTR_LEN(zstr));
|
||||
*len = ZSTR_LEN(zstr);
|
||||
|
||||
zend_string_release(zstr);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
PHP_REDIS_API int
|
||||
redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) {
|
||||
size_t tmplen;
|
||||
int tmpfree;
|
||||
char *tmp;
|
||||
|
||||
/* Don't pack actual numbers if the user asked us not to */
|
||||
if (UNEXPECTED(redis_sock->pack_ignore_numbers &&
|
||||
(Z_TYPE_P(z) == IS_LONG || Z_TYPE_P(z) == IS_DOUBLE)))
|
||||
{
|
||||
return serialize_generic_zval(val, val_len, z);
|
||||
}
|
||||
|
||||
/* First serialize */
|
||||
tmpfree = redis_serialize(redis_sock, z, &tmp, &tmplen);
|
||||
|
||||
@@ -3851,9 +3877,29 @@ redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) {
|
||||
|
||||
PHP_REDIS_API int
|
||||
redis_unpack(RedisSock *redis_sock, const char *src, int srclen, zval *zdst) {
|
||||
zend_long lval;
|
||||
double dval;
|
||||
size_t len;
|
||||
char *buf;
|
||||
|
||||
if (UNEXPECTED((redis_sock->serializer != REDIS_SERIALIZER_NONE &&
|
||||
redis_sock->compression != REDIS_COMPRESSION_NONE) &&
|
||||
redis_sock->pack_ignore_numbers) &&
|
||||
srclen > 0 && srclen < 24)
|
||||
{
|
||||
switch (is_numeric_string(src, srclen, &lval, &dval, 0)) {
|
||||
case IS_LONG:
|
||||
ZVAL_LONG(zdst, lval);
|
||||
return 1;
|
||||
case IS_DOUBLE:
|
||||
ZVAL_DOUBLE(zdst, dval);
|
||||
return 1;
|
||||
default:
|
||||
/* Fallthrough */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Uncompress, then unserialize */
|
||||
if (redis_uncompress(redis_sock, &buf, &len, src, srclen)) {
|
||||
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)
|
||||
*val_len = 5;
|
||||
break;
|
||||
|
||||
default: { /* copy */
|
||||
zend_string *zstr = zval_get_string_func(z);
|
||||
if (ZSTR_IS_INTERNED(zstr)) { // do not reallocate interned strings
|
||||
*val = ZSTR_VAL(zstr);
|
||||
*val_len = ZSTR_LEN(zstr);
|
||||
return 0;
|
||||
}
|
||||
*val = estrndup(ZSTR_VAL(zstr), ZSTR_LEN(zstr));
|
||||
*val_len = ZSTR_LEN(zstr);
|
||||
zend_string_efree(zstr);
|
||||
return 1;
|
||||
}
|
||||
default:
|
||||
return serialize_generic_zval(val, val_len, z);
|
||||
}
|
||||
break;
|
||||
case REDIS_SERIALIZER_PHP:
|
||||
|
||||
@@ -151,6 +151,13 @@ class Redis {
|
||||
*/
|
||||
public const OPT_NULL_MULTIBULK_AS_NULL = UNKNOWN;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @cvalue REDIS_OPT_PACK_IGNORE_NUMBERS
|
||||
*
|
||||
*/
|
||||
public const OPT_PACK_IGNORE_NUMBERS = UNKNOWN;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var int
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: 1f8f22ab9cd1635066463b20ab12d295c11b4ac7 */
|
||||
* Stub hash: 78283cf59cefb411c09adf7a0f0bd234c65327b3 */
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0)
|
||||
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)
|
||||
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);
|
||||
zend_string_release(const_OPT_NULL_MULTIBULK_AS_NULL_name);
|
||||
|
||||
zval const_OPT_PACK_IGNORE_NUMBERS_value;
|
||||
ZVAL_LONG(&const_OPT_PACK_IGNORE_NUMBERS_value, REDIS_OPT_PACK_IGNORE_NUMBERS);
|
||||
zend_string *const_OPT_PACK_IGNORE_NUMBERS_name = zend_string_init_interned("OPT_PACK_IGNORE_NUMBERS", sizeof("OPT_PACK_IGNORE_NUMBERS") - 1, 1);
|
||||
zend_declare_class_constant_ex(class_entry, const_OPT_PACK_IGNORE_NUMBERS_name, &const_OPT_PACK_IGNORE_NUMBERS_value, ZEND_ACC_PUBLIC, NULL);
|
||||
zend_string_release(const_OPT_PACK_IGNORE_NUMBERS_name);
|
||||
|
||||
zval const_SERIALIZER_NONE_value;
|
||||
ZVAL_LONG(&const_SERIALIZER_NONE_value, REDIS_SERIALIZER_NONE);
|
||||
zend_string *const_SERIALIZER_NONE_name = zend_string_init_interned("SERIALIZER_NONE", sizeof("SERIALIZER_NONE") - 1, 1);
|
||||
|
||||
@@ -6147,6 +6147,8 @@ void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS,
|
||||
RETURN_LONG(redis_sock->compression);
|
||||
case REDIS_OPT_COMPRESSION_LEVEL:
|
||||
RETURN_LONG(redis_sock->compression_level);
|
||||
case REDIS_OPT_PACK_IGNORE_NUMBERS:
|
||||
RETURN_BOOL(redis_sock->pack_ignore_numbers);
|
||||
case REDIS_OPT_PREFIX:
|
||||
if (redis_sock->prefix) {
|
||||
RETURN_STRINGL(ZSTR_VAL(redis_sock->prefix), ZSTR_LEN(redis_sock->prefix));
|
||||
@@ -6235,6 +6237,9 @@ void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS,
|
||||
RETURN_TRUE;
|
||||
}
|
||||
break;
|
||||
case REDIS_OPT_PACK_IGNORE_NUMBERS:
|
||||
redis_sock->pack_ignore_numbers = zval_is_true(val);
|
||||
RETURN_TRUE;
|
||||
case REDIS_OPT_COMPRESSION_LEVEL:
|
||||
val_long = zval_get_long(val);
|
||||
redis_sock->compression_level = val_long;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: 1f8f22ab9cd1635066463b20ab12d295c11b4ac7 */
|
||||
* Stub hash: 78283cf59cefb411c09adf7a0f0bd234c65327b3 */
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0)
|
||||
ZEND_ARG_INFO(0, options)
|
||||
@@ -1660,6 +1660,12 @@ static zend_class_entry *register_class_Redis(void)
|
||||
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);
|
||||
zend_string_release(const_OPT_NULL_MULTIBULK_AS_NULL_name);
|
||||
|
||||
zval const_OPT_PACK_IGNORE_NUMBERS_value;
|
||||
ZVAL_LONG(&const_OPT_PACK_IGNORE_NUMBERS_value, REDIS_OPT_PACK_IGNORE_NUMBERS);
|
||||
zend_string *const_OPT_PACK_IGNORE_NUMBERS_name = zend_string_init_interned("OPT_PACK_IGNORE_NUMBERS", sizeof("OPT_PACK_IGNORE_NUMBERS") - 1, 1);
|
||||
zend_declare_class_constant_ex(class_entry, const_OPT_PACK_IGNORE_NUMBERS_name, &const_OPT_PACK_IGNORE_NUMBERS_value, ZEND_ACC_PUBLIC, NULL);
|
||||
zend_string_release(const_OPT_PACK_IGNORE_NUMBERS_name);
|
||||
|
||||
zval const_SERIALIZER_NONE_value;
|
||||
ZVAL_LONG(&const_SERIALIZER_NONE_value, REDIS_SERIALIZER_NONE);
|
||||
zend_string *const_SERIALIZER_NONE_name = zend_string_init_interned("SERIALIZER_NONE", sizeof("SERIALIZER_NONE") - 1, 1);
|
||||
|
||||
@@ -76,7 +76,7 @@ class Redis_Test extends TestSuite {
|
||||
$info = $this->redis->info();
|
||||
$this->version = (isset($info['redis_version'])?$info['redis_version']:'0.0.0');
|
||||
$this->is_keydb = $this->detectKeyDB($info);
|
||||
$this->is_valkey = $this->detectValKey($info);
|
||||
$this->is_valkey = $this->detectValKey($info);
|
||||
}
|
||||
|
||||
protected function minVersionCheck($version) {
|
||||
@@ -4958,6 +4958,104 @@ class Redis_Test extends TestSuite {
|
||||
$this->redis->setOption(Redis::OPT_PREFIX, '');
|
||||
}
|
||||
|
||||
private function cartesianProduct(array $arrays) {
|
||||
$result = [[]];
|
||||
|
||||
foreach ($arrays as $array) {
|
||||
$append = [];
|
||||
foreach ($result as $product) {
|
||||
foreach ($array as $item) {
|
||||
$newProduct = $product;
|
||||
$newProduct[] = $item;
|
||||
$append[] = $newProduct;
|
||||
}
|
||||
}
|
||||
|
||||
$result = $append;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function testIgnoreNumbers() {
|
||||
$combinations = $this->cartesianProduct([
|
||||
[false, true, false],
|
||||
$this->getSerializers(),
|
||||
$this->getCompressors(),
|
||||
]);
|
||||
|
||||
foreach ($combinations as [$ignore, $serializer, $compression]) {
|
||||
$this->redis->setOption(Redis::OPT_PACK_IGNORE_NUMBERS, $ignore);
|
||||
$this->redis->setOption(Redis::OPT_SERIALIZER, $serializer);
|
||||
$this->redis->setOption(Redis::OPT_COMPRESSION, $compression);
|
||||
|
||||
$this->assertIsInt($this->redis->del('answer'));
|
||||
$this->assertIsInt($this->redis->del('hash'));
|
||||
|
||||
$transparent = $compression === Redis::COMPRESSION_NONE &&
|
||||
($serializer === Redis::SERIALIZER_NONE ||
|
||||
$serializer === Redis::SERIALIZER_JSON);
|
||||
|
||||
if ($transparent || $ignore) {
|
||||
$expected_answer = 42;
|
||||
$expected_pi = 3.14;
|
||||
} else {
|
||||
$expected_answer = false;
|
||||
$expected_pi = false;
|
||||
}
|
||||
|
||||
$this->assertTrue($this->redis->set('answer', 32));
|
||||
$this->assertEquals($expected_answer, $this->redis->incr('answer', 10));
|
||||
|
||||
$this->assertTrue($this->redis->set('pi', 3.04));
|
||||
$this->assertEquals($expected_pi, $this->redis->incrByFloat('pi', 0.1));
|
||||
|
||||
$this->assertEquals(1, $this->redis->hset('hash', 'answer', 32));
|
||||
$this->assertEquals($expected_answer, $this->redis->hIncrBy('hash', 'answer', 10));
|
||||
|
||||
$this->assertEquals(1, $this->redis->hset('hash', 'pi', 3.04));
|
||||
$this->assertEquals($expected_pi, $this->redis->hIncrByFloat('hash', 'pi', 0.1));
|
||||
}
|
||||
|
||||
$this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE);
|
||||
$this->redis->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_NONE);
|
||||
$this->redis->setOption(Redis::OPT_PACK_IGNORE_NUMBERS, false);
|
||||
}
|
||||
|
||||
function testIgnoreNumbersReturnTypes() {
|
||||
$combinations = $this->cartesianProduct([
|
||||
[false, true],
|
||||
array_filter($this->getSerializers(), function($s) {
|
||||
return $s !== Redis::SERIALIZER_NONE;
|
||||
}),
|
||||
array_filter($this->getCompressors(), function($c) {
|
||||
return $c !== Redis::COMPRESSION_NONE;
|
||||
}),
|
||||
]);
|
||||
|
||||
foreach ($combinations as [$ignore, $serializer, $compression]) {
|
||||
$this->redis->setOption(Redis::OPT_PACK_IGNORE_NUMBERS, $ignore);
|
||||
$this->redis->setOption(Redis::OPT_SERIALIZER, $serializer);
|
||||
$this->redis->setOption(Redis::OPT_COMPRESSION, $compression);
|
||||
|
||||
foreach ([42, 3.14] as $value) {
|
||||
$this->assertTrue($this->redis->set('key', $value));
|
||||
|
||||
/* There's a known issue in the PHP JSON parser, which
|
||||
can stringify numbers. Unclear the root cause */
|
||||
if ($serializer == Redis::SERIALIZER_JSON) {
|
||||
$this->assertEqualsWeak($value, $this->redis->get('key'));
|
||||
} else {
|
||||
$this->assertEquals($value, $this->redis->get('key'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE);
|
||||
$this->redis->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_NONE);
|
||||
$this->redis->setOption(Redis::OPT_PACK_IGNORE_NUMBERS, false);
|
||||
}
|
||||
|
||||
public function testSerializerIGBinary() {
|
||||
if ( ! defined('Redis::SERIALIZER_IGBINARY'))
|
||||
$this->markTestSkipped('Redis::SERIALIZER_IGBINARY is not defined');
|
||||
|
||||
Reference in New Issue
Block a user