diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 569919c563..5da8559e24 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -190,12 +190,12 @@ jobs: for PORT in {6379..6382} {7000..7005} {32767..32768} {26379..26380}; do until echo PING | ${{ matrix.server }}-cli -p "$PORT" 2>&1 | grep -qE 'PONG|NOAUTH'; do echo "Still waiting for ${{ matrix.server }} on port $PORT" - sleep .05 + sleep .5 done done until echo PING | ${{ matrix.server }}-cli -s /tmp/redis.sock 2>&1 | grep -qE 'PONG|NOAUTH'; do echo "Still waiting for ${{ matrix.server }} at /tmp/redis.sock" - sleep .05 + sleep .5 done - name: Initialize ${{ matrix.server }} cluster diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d1cf0aa4f..fcfb5e7607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,14 +5,176 @@ All changes to phpredis will be documented in this file. We're basing this format on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and PhpRedis adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +# [6.2.0] - 2025-03-24 ([Github](https://github.com/phpredis/phpredis/releases/6.2.0), [PECL](https://pecl.php.net/package/redis/6.2.0)) + +### Sponsors :sparkling_heart: + +- [A-VISION](https://github.com/A-VISION-BV) +- [Avtandil Kikabidze](https://github.com/akalongman) +- [Geoffrey Hoffman](https://github.com/phpguru) +- [Object Cache Pro for WordPress](https://objectcache.pro/) +- [Open LMS](https://openlms.net/) +- [Salvatore Sanfilippo](https://github.com/antirez) +- [Ty Karok](https://github.com/karock) +- [Vanessa Santana](https://github.com/vanessa-dev) + + Special thanks to [Jakub Onderka](https://github.com/jakubonderka) for nearly two dozen performance improvements in this release! + +## Fixed + +- Fix arguments order for `SET` command + [f73f5fc](https://github.com/phpredis/phpredis/commit/f73f5fcce55ab9268c4eb40bf93cccdae418c1d2) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +- Fix error length calculation and UB sanity check + [e73130fe](https://github.com/phpredis/phpredis/commit/e73130fee0c22a20e11ce1596579df3f6f826974) + ([michael-grunder](https://github.com/michael-grunder)) +- Invalidate slot cache on failed cluster connections + [c7b87843](https://github.com/phpredis/phpredis/commit/c7b878431014789f35d2fb1834b95257ca6cbba5) + ([James Kennedy](https://github.com/jkenn99)) +- Don't cast a uint64_t to a long + [faa4bc20](https://github.com/phpredis/phpredis/commit/faa4bc20868c76be4ecc4265015104a8adafccc4) + ([michael-grunder](https://github.com/michael-grunder)) +- Fix potential NULL dereference + [43e6cab8](https://github.com/phpredis/phpredis/commit/43e6cab8792dc01580894d85600add9b68c27a42) + ([peter15914](https://github.com/peter15914)) +- Print cursor as unsigned 64 bit integer + [138d07b6](https://github.com/phpredis/phpredis/commit/138d07b67c5537373834f1cae99804e092db1631) + ([Bentley O'Kane-Chase](https://github.com/bentleyo)) +- Fix XAUTOCLAIM argc when sending COUNT + [0fe45d24](https://github.com/phpredis/phpredis/commit/0fe45d24d4d8c115a5b52846be072ecb9bb43329) + ([michael-grunder](https://github.com/michael-grunder)) ### Added +- Added `serverName()` and `serverVersion()` introspection methods + [056c2dbe](https://github.com/phpredis/phpredis/commit/056c2dbee7f6379a9f546e46584ace59449847c7) + [cbaf095f](https://github.com/phpredis/phpredis/commit/cbaf095ff708caf2728541bd627399a4058d0f19) + [fa3eb006](https://github.com/phpredis/phpredis/commit/fa3eb00683a2c8d539b52c0738db6821c74fef54) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) + ([michael-grunder](https://github.com/michael-grunder)) - Added `getWithMeta` method [9036ffca](https://github.com/phpredis/phpredis/commit/9036ffca) ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +- Implement `GETDEL` command for RedisCluster + [d342e4ac](https://github.com/phpredis/phpredis/commit/d342e4ac18723607b001deb593c8d45e40bbc4c8) + ([michael-grunder](https://github.com/michael-grunder)) +- Introduce `Redis::OPT_PACK_IGNORE_NUMBERS` option + [f9ce9429](https://github.com/phpredis/phpredis/commit/f9ce9429ef9f14a3de2c3fe1d68d02fb7440093d) + [29e5cf0d](https://github.com/phpredis/phpredis/commit/29e5cf0d8c03069aa34c2a63322951fdf2c268c2) + ([michael-grunder](https://github.com/michael-grunder)) +- Implement Valkey >= 8.1 `IFEQ` `SET` option + [a2eef77f](https://github.com/phpredis/phpredis/commit/a2eef77f4419cda815052e75def3af81b0ccd80f) + ([michael-grunder](https://github.com/michael-grunder)) +- Implement KeyDB's EXPIREMEMBER[AT] commands + [4cd3f593](https://github.com/phpredis/phpredis/commit/4cd3f59356582a65aec1cceed44741bd5d161d9e) + ([michael-grunder](https://github.com/michael-grunder)) +- Set priority to 60 (for PIE installations) + [9e504ede](https://github.com/phpredis/phpredis/commit/9e504ede34749326a39f997db6cc5c4201f6a9bc) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +### Documentation + +- Fix phpdoc type of `$pattern` + [5cad2076](https://github.com/phpredis/phpredis/commit/5cad20763710d44f8efb8e537f8f84a812935604) + ([OHZEKI Naoki](https://github.com/zeek0x)) +- Better documentation for the `$tlsOptions` parameter of RedisCluster + [8144db37](https://github.com/phpredis/phpredis/commit/8144db374338006a316beb11549f37926bd40c5d) + ([Jacob Brown](https://github.com/JacobBrownAustin)) + +### Tests/CI + +- Reorganize tests + [807f806f](https://github.com/phpredis/phpredis/commit/807f806f) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +- Add details to the option doc block + [abb0f6cc](https://github.com/phpredis/phpredis/commit/abb0f6ccc827f240a1de53633225abbc2848fc3a) + ([michael-grunder](https://github.com/michael-grunder)) +- Update CodeQL to v3 + [41e11417](https://github.com/phpredis/phpredis/commit/41e114177a20a03e3013db2a3b90980a1f4f1635) + [a10bca35](https://github.com/phpredis/phpredis/commit/a10bca35bba32bb969cc1e473564695d3f8a8811) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +- Add PHP 8.4 to CI + [6097e7ba](https://github.com/phpredis/phpredis/commit/6097e7ba50c0a300bc4f420f84c5d2665ef99d90) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +- Pin ubuntu version for KeyDB + [eb66fc9e](https://github.com/phpredis/phpredis/commit/eb66fc9e2fe60f13e5980ea2ecbe9457ca5ae8b4) + [985b0313](https://github.com/phpredis/phpredis/commit/985b0313fb664c9776c3d2c84e778ddd6733728e) + ([michael-grunder](https://github.com/michael-grunder)) +- Windows CI: update setup-php-sdk to v0.10 and enable caching + [f89d4d8f](https://github.com/phpredis/phpredis/commit/f89d4d8f6eecbe223e158651ffffd77ffa27449b) + ([Christoph M. Becker](https://github.com/cmb69)) + +### Internal/Performance + +- Reduce buffer size for signed integer + [044b3038](https://github.com/phpredis/phpredis/commit/044b30386f0418e9ed2a2bbc3b79582520d008d8) + [35c59880](https://github.com/phpredis/phpredis/commit/35c5988027eda663167a64decde4512957cae738) + ([Bentley O'Kane-Chase](https://github.com/bentleyo)) +- Create a strncmp wrapper + [085d61ec](https://github.com/phpredis/phpredis/commit/085d61ecfb0d484832547b46343a2e4b275a372e) + ([michael-grunder](https://github.com/michael-grunder)) +- Refactor and avoid allocation in rawcommand method + [f68544f7](https://github.com/phpredis/phpredis/commit/f68544f70385e1d431fb0245fafe30b39ee7479a) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Switch from linked list to growing array for reply callbacks + [a551fdc9](https://github.com/phpredis/phpredis/commit/a551fdc94c14d7974f2303cd558f7bd3e0fd91d6) + [42a42769](https://github.com/phpredis/phpredis/commit/42a427695e89577a1f1a554dba268527f3995708) + ([JakubOnderka](https://github.com/JakubOnderka)) + ([michael-grunder](https://github.com/michael-grunder)) +- Reuse redis_sock_append_auth method + [be388562](https://github.com/phpredis/phpredis/commit/be388562058a75ed8fd31926bb0e6a60e2d8cb08) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Switch pipeline_cmd from smart_str to smart_string + [571ffbc8](https://github.com/phpredis/phpredis/commit/571ffbc8e0a5da807a6cc4a2cc5aa90af72e23b0) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Remove unused redis_debug_response method from library.c + [7895636a](https://github.com/phpredis/phpredis/commit/7895636a3a7cd3cad396a83ebe3aa5fe0208f42d) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Optimise HMGET method + [2434ba29](https://github.com/phpredis/phpredis/commit/2434ba294cbb3b2f5b4ee581c37056906902d0d9) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Avoid unnecessary allocation in redis_hset_cmd + [aba09933](https://github.com/phpredis/phpredis/commit/aba09933db05a1a36e947c6fa9dca9889c6a77ff) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Avoid unnecessary allocation in redis_hdel_cmd + [4082dd07](https://github.com/phpredis/phpredis/commit/4082dd07f714fd2f6a0918b1845eb46c403a9edd) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Avoid unnecessary allocation in redis_key_varval_cmd + [99650e15](https://github.com/phpredis/phpredis/commit/99650e15453f03b5dd99284548514551fde4c812) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Use zval_get_tmp_string method that is faster when provided zval is string + [f6906470](https://github.com/phpredis/phpredis/commit/f6906470a52e2d24b1e1b9f2574726643edd7a64) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Optimise constructing Redis command string + [2a2f908f](https://github.com/phpredis/phpredis/commit/2a2f908f2b6b695a0e6705200160e592802f0e41) + ([JakubOnderka](https://github.com/JakubOnderka)) +- If no command is issued in multi mode, return immutable empty array + [5156e032](https://github.com/phpredis/phpredis/commit/5156e0320242ff05f327a3801667140069688c0e) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Test for empty pipeline and multi + [426de2bb](https://github.com/phpredis/phpredis/commit/426de2bb71372f665f5a5bb5a779a7b9c586892d) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Optimise method array_zip_values_and_scores + [400503b8](https://github.com/phpredis/phpredis/commit/400503b8718104b766ceb4a0b84e4a446dbee09b) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Faster parameter parsing in redis_key_cmd and redis_key_long_val_cmd + [83a19656](https://github.com/phpredis/phpredis/commit/83a19656f49aec8f354596099dbf97ba7375d7af) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Use immutable empty array in Redis::hKeys + [3a2f3f45](https://github.com/phpredis/phpredis/commit/3a2f3f45fc7bb01d1be2b9d97cf9d8bff0b0e818) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Use immutable empty array in Redis::exec + [60b5a886](https://github.com/phpredis/phpredis/commit/60b5a8860ae3ff2d02d7f06cc6f86b59cb53b2cf) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Do not allocate empty string or string with one character + [64da891e](https://github.com/phpredis/phpredis/commit/64da891e6fe5810b1aa2a47bc0632a2cd346659d) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Initialize arrays with known size + [99beb922](https://github.com/phpredis/phpredis/commit/99beb9221c815018f1d076654b033cafac22a6ce) + ([JakubOnderka](https://github.com/JakubOnderka)) +- Use smart str for constructing pipeline cmd + [b665925e](https://github.com/phpredis/phpredis/commit/b665925eeddfdf6a6fc1de471c0789ffb60cd067) + ([JakubOnderka](https://github.com/JakubOnderka)) ## [6.1.0] - 2024-10-04 ([Github](https://github.com/phpredis/phpredis/releases/6.1.0), [PECL](https://pecl.php.net/package/redis/6.1.0)) diff --git a/README.md b/README.md index 9f3389d893..61259f0fb6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://github.com/phpredis/phpredis/actions/workflows/ci.yml/badge.svg)](https://github.com/phpredis/phpredis/actions/workflows/ci.yml) [![Coverity Scan Build Status](https://scan.coverity.com/projects/13205/badge.svg)](https://scan.coverity.com/projects/phpredis-phpredis) -[![PHP version](https://img.shields.io/badge/php-%3E%3D%207.0-8892BF.svg)](https://github.com/phpredis/phpredis) +[![PHP version](https://img.shields.io/badge/php-%3E%3D%207.4-8892BF.svg)](https://github.com/phpredis/phpredis) The phpredis extension provides an API for communicating with the [Redis](http://redis.io/) key-value store. It also supports [KeyDB](https://docs.keydb.dev/) and [Valkey](https://valkey.io/), which are open source alternatives to Redis. @@ -187,6 +187,7 @@ $redis = new Redis([ 'port' => 6379, 'connectTimeout' => 2.5, 'auth' => ['phpredis', 'phpredis'], + 'database' => 2, 'ssl' => ['verify_peer' => false], 'backoff' => [ 'algorithm' => Redis::BACKOFF_ALGORITHM_DECORRELATED_JITTER, @@ -203,8 +204,9 @@ $redis = new Redis([ *connectTimeout*: float, value in seconds (default is 0 meaning unlimited) *retryInterval*: int, value in milliseconds (optional) *readTimeout*: float, value in seconds (default is 0 meaning unlimited) -*persistent*: mixed, if value is string then it used as persistend id, else value casts to boolean -*auth*: mixed, authentication information +*persistent*: mixed, if value is string then it used as persistent id, else value casts to boolean +*auth*: mixed, authentication information +*database*: int, database number *ssl*: array, SSL context options ### Class RedisException diff --git a/cluster_library.c b/cluster_library.c index 45a60e2384..38e1a6505b 100644 --- a/cluster_library.c +++ b/cluster_library.c @@ -1672,37 +1672,54 @@ cluster_single_line_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ct } } +static int cluster_bulk_resp_to_zval(redisCluster *c, zval *zdst) { + char *resp; + + if (c->reply_type != TYPE_BULK || + (resp = redis_sock_read_bulk_reply(c->cmd_sock, c->reply_len)) == NULL) + { + if (c->reply_type != TYPE_BULK) + c->reply_len = 0; + ZVAL_FALSE(zdst); + return FAILURE; + } + + redis_unpack(c->flags, resp, c->reply_len, zdst); + + efree(resp); + + return SUCCESS; +} + /* BULK response handler */ PHP_REDIS_API void cluster_bulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, - void *ctx) + void *ctx) { - char *resp; - zval z_unpacked, z_ret, *zv; + zval zret; - // Make sure we can read the response - if (c->reply_type != TYPE_BULK) { - ZVAL_FALSE(&z_unpacked); - c->reply_len = 0; - } else if ((resp = redis_sock_read_bulk_reply(c->cmd_sock, c->reply_len)) == NULL) { - ZVAL_FALSE(&z_unpacked); - } else { - if (!redis_unpack(c->flags, resp, c->reply_len, &z_unpacked)) { - ZVAL_STRINGL_FAST(&z_unpacked, resp, c->reply_len); - } - efree(resp); - } + cluster_bulk_resp_to_zval(c, &zret); - if (c->flags->flags & PHPREDIS_WITH_METADATA) { - redis_with_metadata(&z_ret, &z_unpacked, c->reply_len); - zv = &z_ret; + if (CLUSTER_IS_ATOMIC(c)) { + RETVAL_ZVAL(&zret, 0, 1); } else { - zv = &z_unpacked; + add_next_index_zval(&c->multi_resp, &zret); } +} + +PHP_REDIS_API void +cluster_bulk_withmeta_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, + void *ctx) +{ + zval zbulk, zmeta; + + cluster_bulk_resp_to_zval(c, &zbulk); + + redis_with_metadata(&zmeta, &zbulk, c->reply_len); if (CLUSTER_IS_ATOMIC(c)) { - RETVAL_ZVAL(zv, 0, 1); + RETVAL_ZVAL(&zmeta, 0, 1); } else { - add_next_index_zval(&c->multi_resp, zv); + add_next_index_zval(&c->multi_resp, &zmeta); } } @@ -2834,11 +2851,8 @@ int mbulk_resp_loop(RedisSock *redis_sock, zval *z_result, if (line != NULL) { zval z_unpacked; - if (redis_unpack(redis_sock, line, line_len, &z_unpacked)) { - add_next_index_zval(z_result, &z_unpacked); - } else { - add_next_index_stringl(z_result, line, line_len); - } + redis_unpack(redis_sock, line, line_len, &z_unpacked); + add_next_index_zval(z_result, &z_unpacked); efree(line); } else { add_next_index_bool(z_result, 0); @@ -2853,8 +2867,8 @@ int mbulk_resp_loop_zipstr(RedisSock *redis_sock, zval *z_result, long long count, void *ctx) { char *line, *key = NULL; - int line_len, key_len = 0; long long idx = 0; + int line_len; // Our count will need to be divisible by 2 if (count % 2 != 0) { @@ -2870,15 +2884,11 @@ int mbulk_resp_loop_zipstr(RedisSock *redis_sock, zval *z_result, if (idx++ % 2 == 0) { // Save our key and length key = line; - key_len = line_len; } else { /* Attempt unpacking */ zval z_unpacked; - if (redis_unpack(redis_sock, line, line_len, &z_unpacked)) { - add_assoc_zval(z_result, key, &z_unpacked); - } else { - add_assoc_stringl_ex(z_result, key, key_len, line, line_len); - } + redis_unpack(redis_sock, line, line_len, &z_unpacked); + add_assoc_zval(z_result, key, &z_unpacked); efree(line); efree(key); @@ -2910,14 +2920,11 @@ int mbulk_resp_loop_zipdbl(RedisSock *redis_sock, zval *z_result, key_len = line_len; } else { zval zv, *z = &zv; - if (redis_unpack(redis_sock,key,key_len, z)) { - zend_string *zstr = zval_get_string(z); - add_assoc_double_ex(z_result, ZSTR_VAL(zstr), ZSTR_LEN(zstr), atof(line)); - zend_string_release(zstr); - zval_dtor(z); - } else { - add_assoc_double_ex(z_result, key, key_len, atof(line)); - } + redis_unpack(redis_sock,key,key_len, z); + zend_string *tmp, *zstr = zval_get_tmp_string(z, &tmp); + add_assoc_double_ex(z_result, ZSTR_VAL(zstr), ZSTR_LEN(zstr), atof(line)); + zend_tmp_string_release(tmp); + zval_dtor(z); /* Free our key and line */ efree(key); @@ -2944,11 +2951,8 @@ int mbulk_resp_loop_assoc(RedisSock *redis_sock, zval *z_result, if (line != NULL) { zval z_unpacked; - if (redis_unpack(redis_sock, line, line_len, &z_unpacked)) { - add_assoc_zval_ex(z_result, ZSTR_VAL(zstr), ZSTR_LEN(zstr), &z_unpacked); - } else { - add_assoc_stringl_ex(z_result, ZSTR_VAL(zstr), ZSTR_LEN(zstr), line, line_len); - } + redis_unpack(redis_sock, line, line_len, &z_unpacked); + add_assoc_zval_ex(z_result, ZSTR_VAL(zstr), ZSTR_LEN(zstr), &z_unpacked); efree(line); } else { add_assoc_bool_ex(z_result, ZSTR_VAL(zstr), ZSTR_LEN(zstr), 0); diff --git a/cluster_library.h b/cluster_library.h index 3adfaf00a0..aa5152cb67 100644 --- a/cluster_library.h +++ b/cluster_library.h @@ -432,6 +432,8 @@ PHP_REDIS_API void cluster_single_line_resp(INTERNAL_FUNCTION_PARAMETERS, redisC void *ctx); PHP_REDIS_API void cluster_bulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); +PHP_REDIS_API void cluster_bulk_withmeta_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, + void *ctx); PHP_REDIS_API void cluster_bulk_raw_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_dbl_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, diff --git a/common.h b/common.h index d87da945be..10194a4769 100644 --- a/common.h +++ b/common.h @@ -152,7 +152,6 @@ typedef enum { #define PIPELINE 2 #define PHPREDIS_DEBUG_LOGGING 0 -#define PHPREDIS_WITH_METADATA 1 #if PHP_VERSION_ID < 80000 #define Z_PARAM_ARRAY_HT_OR_NULL(dest) \ diff --git a/library.c b/library.c index fe47f3ff4f..c73858ef51 100644 --- a/library.c +++ b/library.c @@ -88,6 +88,27 @@ } \ } while (0) +/** Set return value to false in case of we are in atomic mode or add FALSE to output array in pipeline mode */ +#define REDIS_RESPONSE_ERROR(redis_sock, z_tab) \ + do { \ + if (IS_ATOMIC(redis_sock)) { \ + RETVAL_FALSE; \ + } else { \ + add_next_index_bool(z_tab, 0); \ + } \ + } while (0) + +/** Set return value to `zval` in case of we are in atomic mode or add `zval` to output array in pipeline mode */ +#define REDIS_RETURN_ZVAL(redis_sock, z_tab, zval) \ + do { \ + if (IS_ATOMIC(redis_sock)) { \ + /* Move value of `zval` to `return_value` */ \ + ZVAL_COPY_VALUE(return_value, &zval); \ + } else { \ + zend_hash_next_index_insert_new(Z_ARRVAL_P(z_tab), &zval); \ + } \ + } while (0) + #ifndef PHP_WIN32 #include /* TCP_NODELAY */ #include /* SO_KEEPALIVE */ @@ -107,13 +128,6 @@ void redis_register_persistent_resource(zend_string *id, void *ptr, int le_id) { zend_register_persistent_resource(ZSTR_VAL(id), ZSTR_LEN(id), ptr, le_id); } -/* Do not allocate empty string or string with one character */ -static zend_always_inline void redis_add_next_index_stringl(zval *arg, const char *str, size_t length) { - zval tmp; - ZVAL_STRINGL_FAST(&tmp, str, length); - zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp); -} - static ConnectionPool * redis_sock_get_connection_pool(RedisSock *redis_sock) { @@ -139,31 +153,40 @@ redis_sock_get_connection_pool(RedisSock *redis_sock) return pool; } -/* Helper to reselect the proper DB number when we reconnect */ -static int reselect_db(RedisSock *redis_sock) { - char *cmd, *response; - int cmd_len, response_len; - - cmd_len = redis_spprintf(redis_sock, NULL, &cmd, "SELECT", "d", - redis_sock->dbNumber); - - if (redis_sock_write(redis_sock, cmd, cmd_len) < 0) { - efree(cmd); - return -1; +static int redis_sock_response_ok(RedisSock *redis_sock, char *buf, int buf_size) { + size_t len; + if (UNEXPECTED(redis_sock_gets(redis_sock, buf, buf_size - 1, &len) < 0)) { + return 0; + } + if (UNEXPECTED(redis_strncmp(buf, ZEND_STRL("+OK")))) { + if (buf[0] == '-') { + // Set error message in case of error + redis_sock_set_err(redis_sock, buf + 1, len); + } + return 0; } + return 1; +} - efree(cmd); +/* Helper to select the proper DB number */ +static int redis_select_db(RedisSock *redis_sock) { + char response[4096]; + smart_string cmd = {0}; - if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) { + REDIS_CMD_INIT_SSTR_STATIC(&cmd, 1, "SELECT"); + redis_cmd_append_sstr_long(&cmd, redis_sock->dbNumber); + + if (redis_sock_write(redis_sock, cmd.c, cmd.len) < 0) { + efree(cmd.c); return -1; } - if (redis_strncmp(response, ZEND_STRL("+OK"))) { - efree(response); + efree(cmd.c); + + if (!redis_sock_response_ok(redis_sock, response, sizeof(response))) { return -1; } - efree(response); return 0; } @@ -241,7 +264,6 @@ redis_sock_auth_cmd(RedisSock *redis_sock, int *cmdlen) { PHP_REDIS_API int redis_sock_auth(RedisSock *redis_sock) { char *cmd, inbuf[4096]; int cmdlen; - size_t len; if ((cmd = redis_sock_auth_cmd(redis_sock, &cmdlen)) == NULL) return SUCCESS; @@ -252,9 +274,7 @@ PHP_REDIS_API int redis_sock_auth(RedisSock *redis_sock) { } efree(cmd); - if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0 || - redis_strncmp(inbuf, ZEND_STRL("+OK"))) - { + if (!redis_sock_response_ok(redis_sock, inbuf, sizeof(inbuf))) { return FAILURE; } return SUCCESS; @@ -384,7 +404,7 @@ redis_check_eof(RedisSock *redis_sock, zend_bool no_retry, zend_bool no_throw) redis_sock->status = REDIS_SOCK_STATUS_AUTHENTICATED; /* If we're using a non-zero db, reselect it */ - if (redis_sock->dbNumber && reselect_db(redis_sock) != 0) { + if (redis_sock->dbNumber && redis_select_db(redis_sock) != 0) { errmsg = "SELECT failed while reconnecting"; break; } @@ -1183,11 +1203,7 @@ redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, double ret; if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) { - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); return FAILURE; } @@ -1208,11 +1224,7 @@ PHP_REDIS_API int redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *r long l; if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) { - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); return FAILURE; } @@ -1293,11 +1305,7 @@ PHP_REDIS_API int redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *r /* Free source response */ efree(response); - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_ret, 0, 1); - } else { - add_next_index_zval(z_tab, &z_ret); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_ret); return SUCCESS; } @@ -1387,11 +1395,7 @@ redis_client_info_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zva efree(resp); /* Return or append depending if we're atomic */ - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_ret, 0, 1); - } else { - add_next_index_zval(z_tab, &z_ret); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_ret); return SUCCESS; } @@ -1421,11 +1425,7 @@ redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zva efree(resp); /* Return or append depending if we're atomic */ - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_ret, 0, 1); - } else { - add_next_index_zval(z_tab, &z_ret); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_ret); return SUCCESS; } @@ -1593,11 +1593,7 @@ redis_lpos_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z res = FAILURE; } - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&zdst, 0, 0); - } else { - add_next_index_zval(z_tab, &zdst); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, zdst); return res; } @@ -1657,11 +1653,7 @@ PHP_REDIS_API int redis_long_response(INTERNAL_FUNCTION_PARAMETERS, int response_len; if ((response = redis_sock_read(redis_sock, &response_len)) == NULL || *response != TYPE_INT) { - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); if (response) efree(response); return FAILURE; } @@ -1790,11 +1782,7 @@ redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, int numElems; if (read_mbulk_header(redis_sock, &numElems) < 0) { - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); return FAILURE; } zval z_multi_result; @@ -1811,11 +1799,7 @@ redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, array_zip_values_and_scores(redis_sock, &z_multi_result, decode); } - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_multi_result, 0, 1); - } else { - add_next_index_zval(z_tab, &z_multi_result); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_multi_result); return 0; } @@ -1904,11 +1888,7 @@ redis_mpop_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, ZVAL_FALSE(&zret); } - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&zret, 0, 0); - } else { - add_next_index_zval(z_tab, &zret); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, zret); return res; } @@ -1988,11 +1968,7 @@ redis_geosearch_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, ZVAL_FALSE(&zret); } - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&zret, 0, 1); - } else { - add_next_index_zval(z_tab, &zret); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, zret); return SUCCESS; } @@ -2004,11 +1980,7 @@ redis_client_trackinginfo_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_s zval z_ret; if (read_mbulk_header(redis_sock, &numElems) < 0) { - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); return FAILURE; } @@ -2016,11 +1988,7 @@ redis_client_trackinginfo_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_s redis_read_multibulk_recursive(redis_sock, numElems, 0, &z_ret); array_zip_values_and_scores(redis_sock, &z_ret, 0); - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_ret, 0, 1); - } else { - add_next_index_zval(z_tab, &z_ret); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_ret); return SUCCESS; } @@ -2050,26 +2018,31 @@ static int redis_hello_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { - int numElems; zval z_ret, *zv; + int numElems; - if (read_mbulk_header(redis_sock, &numElems) < 0) { - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } - return FAILURE; - } + if (read_mbulk_header(redis_sock, &numElems) < 0) + goto fail; array_init(&z_ret); - redis_mbulk_reply_zipped_raw_variant(redis_sock, &z_ret, numElems); + + if (redis_read_multibulk_recursive(redis_sock, numElems, 0, &z_ret) != SUCCESS || + array_zip_values_recursive(&z_ret) != SUCCESS) + { + zval_dtor(&z_ret); + goto fail; + } if (redis_sock->hello.server) { zend_string_release(redis_sock->hello.server); } - zv = zend_hash_str_find(Z_ARRVAL(z_ret), ZEND_STRL("server")); - redis_sock->hello.server = zv ? zval_get_string(zv) : ZSTR_EMPTY_ALLOC(); + + if ((zv = zend_hash_str_find(Z_ARRVAL(z_ret), ZEND_STRL("dragonfly_version")))) { + redis_sock->hello.server = zend_string_init(ZEND_STRL("dragonfly"), 0); + } else { + zv = zend_hash_str_find(Z_ARRVAL(z_ret), ZEND_STRL("server")); + redis_sock->hello.server = zv ? zval_get_string(zv) : ZSTR_EMPTY_ALLOC(); + } if (redis_sock->hello.version) { zend_string_release(redis_sock->hello.version); @@ -2095,6 +2068,14 @@ redis_hello_response(INTERNAL_FUNCTION_PARAMETERS, } return SUCCESS; + +fail: + if (IS_ATOMIC(redis_sock)) { + RETVAL_FALSE; + } else { + add_next_index_bool(z_tab, 0); + } + return FAILURE; } @@ -2121,11 +2102,7 @@ redis_function_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval * zval z_ret; if (read_mbulk_header(redis_sock, &numElems) < 0) { - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); return FAILURE; } @@ -2133,11 +2110,7 @@ redis_function_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval * redis_read_multibulk_recursive(redis_sock, numElems, 0, &z_ret); array_zip_values_recursive(&z_ret); - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_ret, 0, 1); - } else { - add_next_index_zval(z_tab, &z_ret); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_ret); return SUCCESS; } @@ -2165,21 +2138,13 @@ redis_command_info_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zv zval z_ret; if (read_mbulk_header(redis_sock, &numElems) < 0) { - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); return FAILURE; } array_init(&z_ret); redis_read_multibulk_recursive(redis_sock, numElems, 0, &z_ret); - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_ret, 0, 1); - } else { - add_next_index_zval(z_tab, &z_ret); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_ret); return SUCCESS; } @@ -2250,19 +2215,11 @@ redis_xrange_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, redis_read_stream_messages(redis_sock, messages, &z_messages) < 0) { zval_dtor(&z_messages); - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); return -1; } - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_messages, 0, 1); - } else { - add_next_index_zval(z_tab, &z_messages); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_messages); return 0; } @@ -2319,21 +2276,13 @@ redis_xread_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, goto cleanup; } - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_rv, 0, 1); - } else { - add_next_index_zval(z_tab, &z_rv); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_rv); return 0; cleanup: zval_dtor(&z_rv); failure: - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); return -1; } @@ -2460,20 +2409,12 @@ redis_xclaim_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, if (redis_read_xclaim_reply(redis_sock, count, ctx == PHPREDIS_CTX_PTR, &z_ret) < 0) goto failure; - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_ret, 0, 1); - } else { - add_next_index_zval(z_tab, &z_ret); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_ret); return 0; failure: - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); return -1; } @@ -2551,20 +2492,13 @@ redis_xinfo_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_t if (read_mbulk_header(redis_sock, &elements) == SUCCESS) { array_init(&z_ret); if (redis_read_xinfo_response(redis_sock, &z_ret, elements) == SUCCESS) { - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_ret, 0, 1); - } else { - add_next_index_zval(z_tab, &z_ret); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_ret); return SUCCESS; } zval_dtor(&z_ret); } - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + + REDIS_RESPONSE_ERROR(redis_sock, z_tab); return FAILURE; } @@ -2662,11 +2596,7 @@ int redis_acl_custom_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, ZVAL_FALSE(&zret); } - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&zret, 0, 0); - } else { - add_next_index_zval(z_tab, &zret); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, zret); return res; } @@ -2733,35 +2663,53 @@ redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_ta return ret ? SUCCESS : FAILURE; } -PHP_REDIS_API int redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { +static int redis_bulk_resp_to_zval(RedisSock *redis_sock, zval *zdst, int *dstlen) { + char *resp; + int len; - char *response; - int response_len; - zval z_unpacked, z_ret, *zv; - zend_bool ret; + resp = redis_sock_read(redis_sock, &len); + if (dstlen) *dstlen = len; - if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) { - ZVAL_FALSE(&z_unpacked); - ret = FAILURE; - } else { - if (!redis_unpack(redis_sock, response, response_len, &z_unpacked)) { - ZVAL_STRINGL_FAST(&z_unpacked, response, response_len); - } - efree(response); - ret = SUCCESS; + if (resp == NULL) { + ZVAL_FALSE(zdst); + return FAILURE; } - if (redis_sock->flags & PHPREDIS_WITH_METADATA) { - redis_with_metadata(&z_ret, &z_unpacked, response_len); - zv = &z_ret; - } else { - zv = &z_unpacked; - } + redis_unpack(redis_sock, resp, len, zdst); + + efree(resp); + return SUCCESS; +} + +PHP_REDIS_API int +redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + zval *z_tab, void *ctx) +{ + zval zret; + int ret; + + ret = redis_bulk_resp_to_zval(redis_sock, &zret, NULL); + + REDIS_RETURN_ZVAL(redis_sock, z_tab, zret); + + return ret; +} + +PHP_REDIS_API int +redis_bulk_withmeta_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + zval *z_tab, void *ctx) +{ + zval zret, zbulk; + int len, ret; + + ret = redis_bulk_resp_to_zval(redis_sock, &zbulk, &len); + + redis_with_metadata(&zret, &zbulk, len); if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(zv, 0, 1); + RETVAL_ZVAL(&zret, 0, 1); } else { - add_next_index_zval(z_tab, zv); + add_next_index_zval(z_tab, &zret); } return ret; @@ -2779,11 +2727,7 @@ redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) { - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); return FAILURE; } if (IS_ATOMIC(redis_sock)) { @@ -2857,6 +2801,12 @@ redis_sock_configure(RedisSock *redis_sock, HashTable *opts) return FAILURE; } redis_sock_set_auth_zval(redis_sock, val); + } else if (zend_string_equals_literal_ci(zkey, "database")) { + if (Z_TYPE_P(val) != IS_LONG || Z_LVAL_P(val) < 0 || Z_LVAL_P(val) > INT_MAX) { + REDIS_VALUE_EXCEPTION("Invalid database number"); + return FAILURE; + } + redis_sock->dbNumber = Z_LVAL_P(val); } else if (zend_string_equals_literal_ci(zkey, "backoff")) { if (redis_sock_set_backoff(redis_sock, val) != SUCCESS) { REDIS_VALUE_EXCEPTION("Invalid backoff options"); @@ -3229,7 +3179,7 @@ redis_sock_server_open(RedisSock *redis_sock) redis_sock->status = REDIS_SOCK_STATUS_AUTHENTICATED; // fall through case REDIS_SOCK_STATUS_AUTHENTICATED: - if (redis_sock->dbNumber && reselect_db(redis_sock) != SUCCESS) { + if (redis_sock->dbNumber && redis_select_db(redis_sock) != SUCCESS) { break; } redis_sock->status = REDIS_SOCK_STATUS_READY; @@ -3366,11 +3316,7 @@ PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, int numElems; if (read_mbulk_header(redis_sock, &numElems) < 0) { - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); return FAILURE; } if (numElems == -1 && redis_sock->null_mbulk_as_null) { @@ -3382,11 +3328,7 @@ PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, redis_mbulk_reply_loop(redis_sock, &z_multi_result, numElems, UNSERIALIZE_ALL); } - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_multi_result, 0, 1); - } else { - add_next_index_zval(z_tab, &z_multi_result); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_multi_result); return 0; } @@ -3399,11 +3341,7 @@ redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval int numElems; if (read_mbulk_header(redis_sock, &numElems) < 0) { - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); return FAILURE; } zval z_multi_result; @@ -3415,11 +3353,7 @@ redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval redis_mbulk_reply_loop(redis_sock, &z_multi_result, numElems, UNSERIALIZE_NONE); } - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_multi_result, 0, 1); - } else { - add_next_index_zval(z_tab, &z_multi_result); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_multi_result); return SUCCESS; } @@ -3432,11 +3366,7 @@ redis_mbulk_reply_double(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zv zval z_multi_result; if (read_mbulk_header(redis_sock, &numElems) < 0) { - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); return FAILURE; } @@ -3454,11 +3384,7 @@ redis_mbulk_reply_double(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zv } } - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_multi_result, 0, 1); - } else { - add_next_index_zval(z_tab, &z_multi_result); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_multi_result); return SUCCESS; } @@ -3467,7 +3393,7 @@ PHP_REDIS_API void redis_mbulk_reply_loop(RedisSock *redis_sock, zval *z_tab, int count, int unserialize) { - zval z_unpacked; + zval z_value; char *line; int i, len; @@ -3486,11 +3412,13 @@ redis_mbulk_reply_loop(RedisSock *redis_sock, zval *z_tab, int count, (unserialize == UNSERIALIZE_VALS && i % 2 != 0) ); - if (unwrap && redis_unpack(redis_sock, line, len, &z_unpacked)) { - add_next_index_zval(z_tab, &z_unpacked); + if (unwrap) { + redis_unpack(redis_sock, line, len, &z_value); } else { - redis_add_next_index_stringl(z_tab, line, len); + ZVAL_STRINGL_FAST(&z_value, line, len); } + zend_hash_next_index_insert_new(Z_ARRVAL_P(z_tab), &z_value); + efree(line); } } @@ -3557,11 +3485,7 @@ PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSoc zval *z_keys = ctx; if (read_mbulk_header(redis_sock, &numElems) < 0) { - if (IS_ATOMIC(redis_sock)) { - RETVAL_FALSE; - } else { - add_next_index_bool(z_tab, 0); - } + REDIS_RESPONSE_ERROR(redis_sock, z_tab); retval = FAILURE; goto end; } @@ -3575,9 +3499,7 @@ PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSoc response = redis_sock_read(redis_sock, &response_len); zval z_unpacked; if (response != NULL) { - if (!redis_unpack(redis_sock, response, response_len, &z_unpacked)) { - ZVAL_STRINGL(&z_unpacked, response, response_len); - } + redis_unpack(redis_sock, response, response_len, &z_unpacked); efree(response); } else { ZVAL_FALSE(&z_unpacked); @@ -3586,11 +3508,7 @@ PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSoc zend_tmp_string_release(tmp_str); } - if (IS_ATOMIC(redis_sock)) { - RETVAL_ZVAL(&z_multi_result, 0, 1); - } else { - add_next_index_zval(z_tab, &z_multi_result); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_multi_result); retval = SUCCESS; @@ -3984,6 +3902,12 @@ redis_unpack(RedisSock *redis_sock, const char *src, int srclen, zval *zdst) { } } + /* Input string is empty */ + if (srclen == 0) { + ZVAL_STR(zdst, ZSTR_EMPTY_ALLOC()); + return 1; + } + /* Uncompress, then unserialize */ if (redis_uncompress(redis_sock, &buf, &len, src, srclen)) { if (!redis_unserialize(redis_sock, buf, len, zdst)) { @@ -3993,7 +3917,11 @@ redis_unpack(RedisSock *redis_sock, const char *src, int srclen, zval *zdst) { return 1; } - return redis_unserialize(redis_sock, buf, len, zdst); + if (!redis_unserialize(redis_sock, src, srclen, zdst)) { + ZVAL_STRINGL_FAST(zdst, src, srclen); + } + + return 1; } PHP_REDIS_API int @@ -4387,7 +4315,7 @@ redis_read_multibulk_recursive(RedisSock *redis_sock, long long elements, int st elements--; } - return 0; + return SUCCESS; } static int @@ -4435,12 +4363,7 @@ variant_reply_generic(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, return FAILURE; } - if (IS_ATOMIC(redis_sock)) { - /* Set our return value */ - RETVAL_ZVAL(&z_ret, 0, 1); - } else { - add_next_index_zval(z_tab, &z_ret); - } + REDIS_RETURN_ZVAL(redis_sock, z_tab, z_ret); /* Success */ return 0; diff --git a/library.h b/library.h index 5f1806c594..feb310442c 100644 --- a/library.h +++ b/library.h @@ -73,6 +73,7 @@ PHP_REDIS_API int redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, Redi PHP_REDIS_API int redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_bulk_withmeta_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_config_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); diff --git a/package.xml b/package.xml index 5dd79f8196..1aa75ec7b2 100644 --- a/package.xml +++ b/package.xml @@ -22,10 +22,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> p.yatsukhnenko@gmail.com yes - 2024-10-04 + 2025-03-24 - 6.1.0 - 6.0.0 + 6.2.0 + 6.2.0 stable @@ -33,147 +33,75 @@ http://pear.php.net/dtd/package-2.0.xsd"> PHP - Sponsors + --- Sponsors --- + A-VISION Advisering - https://a-vision.nu/ Audiomack - https://audiomack.com - Open LMS - https://openlms.net Avtandil Kikabidze - https://github.com/akalongman - Ty Karok - https://github.com/karock + Geoffrey Hoffman - https://github.com/phpguru Object Cache Pro for WordPress - https://objectcache.pro + Open LMS - https://openlms.net + Salvatore Sanfilippo - https://github.com/antirez + Ty Karok - https://github.com/karock + Vanessa Santana - https://github.com/vanessa-dev - --- 6.1.0 --- - - NOTE: There were no changes to C code between 6.1.0RC2 and 6.1.0 - - Documentation: - - * Update package.xml to make it clearer that we support many key-value stores - [52e69ede] (Remi Collet) - * Fix redis.io urls [0bae4bb0] (Vincent Langlet) - - Tests/CI: - - * Fix 2 tests with redis 6.2 [cc1be322] (Remi Collet) - - --- 6.1.0RC2 --- - - Fixed: - - * Fixed a `SIGABRT` error in PHP 8.4 [a75a7e5a] (Michael Grunder) - * Clean up code for unsupported versions of PHP [37cebdd7] (Remi Collet) - * Add `SessionHelpers.php` to `package.xml`[e9474b80] (Remi Collet) - * 8.4 implicit null fix, bump version [bff3a22e, 30c8f90c] [Remi Collet] - - Changed: - - * Raised minimum supported PHP version to 7.4 [8b519423] (Michael Grunder) - - Removed: - - * Removed erroneously duplicated changelog entries [40c89736] (Michael Grunder) - - Tests/CI: - - * Move to upload artifacts v4 [9d380500] (Michael Grunder) - - Added: - - * Added `composer.json` to support PIE (PHP Installer for Extensions) [b59e35a6] - (James Titcumb) + * A special thanks to Jakub Onderka for nearly two dozen performance improvements in this release! - --- 6.1.0RC1 --- + --- 6.2.0 --- Fixed: - - * Fix random connection timeouts with Redis Cluster. [eb7f31e7] (Jozsef Koszo) - * Fix argument count issue in HSET with associative array [6ea5b3e0] - (Viktor Djupsjobacka) - * SRANDMEMBER can return any type because of serialization. [6673b5b2] - (Michael Grunder) - * Fix HRANDFIELD command when WITHVALUES is used. [99f9fd83] (Michael Grunder) - * Allow context array to be nullable [50529f56] (Michael Grunder) - * Fix a macOS (M1) compiler warning. [7de29d57] (Michael Grunder) - * `GETEX` documentation/updates and implentation in `RedisCluster` [981c6931] - (Michael Grunder) - * Refactor redis_script_cmd and fix to `flush` subcommand. [7c551424] - (Pavlo Yatsukhnenko) - * Update liveness check and fix PHP 8.4 compilation error. [c139de3a] - (Michael Grunder) - * Rework how we declare ZSTD min/max constants. [34b5bd81] (Michael Grunder) - * Fix memory leak if we fail in ps_open_redis. [0e926165] (Michael Grunder) - * Fix segfault and remove redundant macros [a9e53fd1] (Pavlo Yatsukhnenko) - * Fix PHP 8.4 includes [a51215ce] (Michael Grunder) - * Handle arbitrarily large `SCAN` cursors properly. [2612d444, e52f0afa] - (Michael Grunder) - * Improve warning when we encounter an invalid EXPIRY in SET [732e466a] - (Michael Grunder) - * Fix Arginfo / zpp mismatch for DUMP command [50e5405c] (Pavlo Yatsukhnenko) - * RedisCluster::publish returns a cluster_long_resp [14f93339] (Alexandre Choura) - * Fix segfault when passing just false to auth. [6dc0a0be] (Michael Grunder) - * the VALUE argument type for hSetNx must be the same as for hSet [df074dbe] - (Uladzimir Tsykun) - * Other fixes [e18f6c6d, 3d7be358, 2b555c89, fa1a283a, 37c5f8d4] (Michael Grunder, Viktor Szepe) + * Fix arguments order for SET command [f73f5fc] (Pavlo Yatsukhnenko) + * Fix error length calculation and UB sanity check [e73130fe] (michael-grunder) + * Invalidate slot cache on failed cluster connections [c7b87843] (James Kennedy) + * Don't cast a uint64_t to a long [faa4bc20] (michael-grunder) + * Fix potential NULL dereference [43e6cab8] (peter15914) + * Print cursor as unsigned 64 bit integer [138d07b6] (Bentley O'Kane-Chase) + * Fix XAUTOCLAIM argc when sending COUNT [0fe45d24] (michael-grunder) Added: - - * Compression support for PHP sessions. [da4ab0a7] (bitactive) - * Support for early_refresh in Redis sessions to match cluster behavior - [b6989018] (Bitactive) - * Implement WAITAOF command. [ed7c9f6f] (Michael Grunder) - - Removed: - - * PHP 7.1, 7.2, and 7.3 CI jobs [d68c30f8, dc39bd55] (Michael Grunder) - - Changed: - - * Fix the time unit of retry_interval [3fdd52b4] (woodong) + * Added `serverName()` and `serverVersion()` [fa3eb006, cbaf095f, 056c2dbe] + (Pavlo Yatsukhnenko, Michael Grunder) + * Added getWithMeta method [9036ffca, 36ab5850] (Pavlo Yatsukhnenko) + * Implement GETDEL command for RedisCluster [d342e4ac] (michael-grunder) + * Introduce Redis::OPT_PACK_IGNORE_NUMBERS option [f9ce9429, 29e5cf0d] (michael-grunder) + * Implement Valkey >= 8.1 IFEQ SET option [a2eef77f] (michael-grunder) + * Implement KeyDB's EXPIREMEMBER[AT] commands [4cd3f593] (michael-grunder) Documentation: - - * Many documentation fixes. [eeb51099] (Michael Dwyer) - * fix missing code tags [f865d5b9] (divinity76) - * Mention Valkey support [5f1eecfb] (PlavorSeol) - * Mention KeyDB support in README.md [37fa3592] (Tim Starling) - * Remove mention of pickle [c7a73abb] (David Baker) - * Add session.save_path examples [8a39caeb] (Martin Vancl) - * Tighter return types for Redis::(keys|hKeys|hVals|hGetAll) [77ab62bc] - (Benjamin Morel) - * Update stubs [4d233977, ff305349, 12966a74, a4a283ab, 8f8ff72a] - (Michael Grunder, Takayasu Oyama, Pavlo Yatsukhnenko) - * Fix config.m4 when using custom dep paths [ece3f7be] (Michael Grunder) - * Fix retry_internal documentation [142c1f4a] (SplotyCode) - * Fix anchor link [9b5cad31] (Git'Fellow) - * Fix typo in link [bfd379f0] (deiga) - * Fix Fedora package url [60b1ba14, 717713e1] (Dmitrii Kotov) - * Update Redis Sentinel documentation to reflect changes to constructor in 6.0 - release [dc05d65c] (Pavlo Yatsukhnenko) + * Fix phpdoc type of $pattern [5cad2076] (OHZEKI Naoki) + * Better documentation for the $tlsOptions parameter of RedisCluster [8144db37] (Jacob Brown) Tests/CI: - - * Avoid fatal error in test execution. [57304970] (Michael Grunder) - * Refactor unit test framework. [b1771def] (Michael Grunder) - * Get unit tests working in `php-cgi`. [b808cc60] (Michael Grunder) - * Switch to `ZEND_STRL` in more places. [7050c989, f8c762e7] (Michael Grunder) - * Workaround weird PHP compiler crash. [d3b2d87b] (Michael Grunder) - * Refactor tests (formatting, modernization, etc). [dab6a62d, c6cd665b, 78b70ca8, - 3c125b09, 18b0da72, b88e72b1, 0f94d9c1, 59965971, 3dbc2bd8, 9b90c03b, c0d6f042] - (Michael Grunder) - * Spelling fixes [0d89e928] (Michael Grunder) - * Added Valkey support. [f350dc34] (Michael Grunder) - * Add a test for session compression. [9f3ca98c] (Michael Grunder) - * Test against valkey [a819a44b] (Michael Grunder) - * sessionSaveHandler injection. [9f8f80ca] (Pavlo Yatsukhnenko) - * KeyDB addiions [54d62c72, d9c48b78] (Michael Grunder) - * Add PHP 8.3 to CI [78d15140, e051a5db] (Robert Kelcak, Pavlo Yatsukhnenko) - * Use newInstance in RedisClusterTest [954fbab8] (Pavlo Yatsukhnenko) - * Use actions/checkout@v4 [f4c2ac26] (Pavlo Yatsukhnenko) - * Cluster nodes from ENV [eda39958, 0672703b] (Pavlo Yatsukhnenko) - * Ensure we're talking to redis-server in our high ports test. [7825efbc] - (Michael Grunder) - * Add missing option to installation example [2bddd84f] (Pavlo Yatsukhnenko) - * Fix typo in link [8f6bc98f] (Timo Sand) - * Update tests to allow users to use a custom class. [5f6ce414] (Michael Grunder) + * Add details to the option doc block [abb0f6cc] (michael-grunder) + * Update CodeQL to v3 [41e11417, a10bca35] (Pavlo Yatsukhnenko) + * Add PHP 8.4 to CI [6097e7ba] (Pavlo Yatsukhnenko) + * Pin ubuntu version for KeyDB [eb66fc9e, 985b0313] (michael-grunder) + * Windows CI: update setup-php-sdk to v0.10 and enable caching [f89d4d8f] (Christoph M. Becker) + + Internal: + * Reduce buffer size for signed integer [044b3038, 35c59880] (Bentley O'Kane-Chase) + * Create a strncmp wrapper [085d61ec] (michael-grunder) + * Refactor and avoid allocation in rawcommand method [f68544f7] (Jakub Onderka) + * Use defines for callback growth + sanity check [42a42769] (michael-grunder) + * Switch from linked list to growing array for reply callbacks [a551fdc9] (Jakub Onderka) + * Reuse redis_sock_append_auth method [be388562] (Jakub Onderka) + * Switch pipeline_cmd from smart_str to smart_string [571ffbc8] (Jakub Onderka) + * Remove unused redis_debug_response method from library.c [7895636a] (Jakub Onderka) + * Optimise HMGET method [2434ba29] (Jakub Onderka) + * Avoid unnecessary allocation in redis_hset_cmd [aba09933] (Jakub Onderka) + * Avoid unnecessary allocation in redis_hdel_cmd [4082dd07] (Jakub Onderka) + * Avoid unnecessary allocation in redis_key_varval_cmd [99650e15] (Jakub Onderka) + * Use zval_get_tmp_string method that is faster when provided zval is string [f6906470] (Jakub Onderka) + * Optimise constructing Redis command string [2a2f908f] (Jakub Onderka) + * If no command is issued in multi mode, return immutable empty array [5156e032] (Jakub Onderka) + * Test for empty pipeline and multi [426de2bb] (Jakub Onderka) + * Optimise method array_zip_values_and_scores [400503b8] (Jakub Onderka) + * Faster parameter parsing in redis_key_cmd and redis_key_long_val_cmd [83a19656] (Jakub Onderka) + * Use immutable empty array in Redis::hKeys [3a2f3f45] (Jakub Onderka) + * Use immutable empty array in Redis::exec [60b5a886] (Jakub Onderka) + * Do not allocate empty string or string with one character [64da891e] (Jakub Onderka) + * Initialize arrays with known size [99beb922] (Jakub Onderka) + * Use smart str for constructing pipeline cmd [b665925e] (Jakub Onderka) @@ -266,6 +194,83 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + stablestable + 6.2.06.2.0 + 2025-03-24 + + --- Sponsors --- + + A-VISION Advisering - https://a-vision.nu/ + Audiomack - https://audiomack.com + Avtandil Kikabidze - https://github.com/akalongman + Geoffrey Hoffman - https://github.com/phpguru + Object Cache Pro for WordPress - https://objectcache.pro + Open LMS - https://openlms.net + Salvatore Sanfilippo - https://github.com/antirez + Ty Karok - https://github.com/karock + Vanessa Santana - https://github.com/vanessa-dev + + * Special thanks to Jakub Onderka for nearly two dozen performance improvements in this release! + + --- 6.2.0 --- + + Fixed: + * Fix arguments order for SET command [f73f5fc] (Pavlo Yatsukhnenko) + * Fix error length calculation and UB sanity check [e73130fe] (michael-grunder) + * Invalidate slot cache on failed cluster connections [c7b87843] (James Kennedy) + * Don't cast a uint64_t to a long [faa4bc20] (michael-grunder) + * Fix potential NULL dereference [43e6cab8] (peter15914) + * Print cursor as unsigned 64 bit integer [138d07b6] (Bentley O'Kane-Chase) + * Fix XAUTOCLAIM argc when sending COUNT [0fe45d24] (michael-grunder) + + Added: + * Added `serverName()` and `serverVersion()` [fa3eb006, cbaf095f, 056c2dbe] + (Pavlo Yatsukhnenko, Michael Grunder) + * Added getWithMeta method [9036ffca, 36ab5850] (Pavlo Yatsukhnenko) + * Implement GETDEL command for RedisCluster [d342e4ac] (michael-grunder) + * Introduce Redis::OPT_PACK_IGNORE_NUMBERS option [f9ce9429, 29e5cf0d] (michael-grunder) + * Implement Valkey >= 8.1 IFEQ SET option [a2eef77f] (michael-grunder) + * Implement KeyDB's EXPIREMEMBER[AT] commands [4cd3f593] (michael-grunder) + * Set priority to 60 (for PIE installations) [9e504ede] (Pavlo Yatsukhnenko) + + Documentation: + * Fix phpdoc type of $pattern [5cad2076] (OHZEKI Naoki) + * Better documentation for the $tlsOptions parameter of RedisCluster [8144db37] (Jacob Brown) + + Tests/CI: + * Add details to the option doc block [abb0f6cc] (michael-grunder) + * Update CodeQL to v3 [41e11417, a10bca35] (Pavlo Yatsukhnenko) + * Add PHP 8.4 to CI [6097e7ba] (Pavlo Yatsukhnenko) + * Pin ubuntu version for KeyDB [eb66fc9e, 985b0313] (michael-grunder) + * Windows CI: update setup-php-sdk to v0.10 and enable caching [f89d4d8f] (Christoph M. Becker) + + Internal/Performance: + * Reduce buffer size for signed integer [044b3038, 35c59880] (Bentley O'Kane-Chase) + * Create a strncmp wrapper [085d61ec] (michael-grunder) + * Refactor and avoid allocation in rawcommand method [f68544f7] (Jakub Onderka) + * Use defines for callback growth + sanity check [42a42769] (michael-grunder) + * Switch from linked list to growing array for reply callbacks [a551fdc9] (Jakub Onderka) + * Reuse redis_sock_append_auth method [be388562] (Jakub Onderka) + * Switch pipeline_cmd from smart_str to smart_string [571ffbc8] (Jakub Onderka) + * Remove unused redis_debug_response method from library.c [7895636a] (Jakub Onderka) + * Optimise HMGET method [2434ba29] (Jakub Onderka) + * Avoid unnecessary allocation in redis_hset_cmd [aba09933] (Jakub Onderka) + * Avoid unnecessary allocation in redis_hdel_cmd [4082dd07] (Jakub Onderka) + * Avoid unnecessary allocation in redis_key_varval_cmd [99650e15] (Jakub Onderka) + * Use zval_get_tmp_string method that is faster when provided zval is string [f6906470] (Jakub Onderka) + * Optimise constructing Redis command string [2a2f908f] (Jakub Onderka) + * If no command is issued in multi mode, return immutable empty array [5156e032] (Jakub Onderka) + * Test for empty pipeline and multi [426de2bb] (Jakub Onderka) + * Optimise method array_zip_values_and_scores [400503b8] (Jakub Onderka) + * Faster parameter parsing in redis_key_cmd and redis_key_long_val_cmd [83a19656] (Jakub Onderka) + * Use immutable empty array in Redis::hKeys [3a2f3f45] (Jakub Onderka) + * Use immutable empty array in Redis::exec [60b5a886] (Jakub Onderka) + * Do not allocate empty string or string with one character [64da891e] (Jakub Onderka) + * Initialize arrays with known size [99beb922] (Jakub Onderka) + * Use smart str for constructing pipeline cmd [b665925e] (Jakub Onderka) + + stablestable 6.1.06.0.0 diff --git a/php_redis.h b/php_redis.h index 77f31498c6..8f535cb6fe 100644 --- a/php_redis.h +++ b/php_redis.h @@ -23,7 +23,7 @@ #define PHP_REDIS_H /* phpredis version */ -#define PHP_REDIS_VERSION "6.1.0" +#define PHP_REDIS_VERSION "6.2.0" /* For convenience we store the salt as a printable hex string which requires 2 * characters per byte + 1 for the NULL terminator */ diff --git a/redis.c b/redis.c index 3075437d1a..2ef2fc8fa9 100644 --- a/redis.c +++ b/redis.c @@ -239,6 +239,31 @@ redis_sock_get_instance(zval *id, int no_throw) return NULL; } +static zend_never_inline ZEND_COLD void redis_sock_throw_exception(RedisSock *redis_sock) { + char *errmsg = NULL; + if (redis_sock->status == REDIS_SOCK_STATUS_AUTHENTICATED) { + if (redis_sock->err != NULL) { + spprintf(&errmsg, 0, "Could not select database %ld '%s'", redis_sock->dbNumber, ZSTR_VAL(redis_sock->err)); + } else { + spprintf(&errmsg, 0, "Could not select database %ld", redis_sock->dbNumber); + } + } else if (redis_sock->status == REDIS_SOCK_STATUS_CONNECTED) { + if (redis_sock->err != NULL) { + spprintf(&errmsg, 0, "Could not authenticate '%s'", ZSTR_VAL(redis_sock->err)); + } else { + spprintf(&errmsg, 0, "Could not authenticate"); + } + } else { + if (redis_sock->port < 0) { + spprintf(&errmsg, 0, "Redis server %s went away", ZSTR_VAL(redis_sock->host)); + } else { + spprintf(&errmsg, 0, "Redis server %s:%d went away", ZSTR_VAL(redis_sock->host), redis_sock->port); + } + } + REDIS_THROW_EXCEPTION(errmsg, 0); + efree(errmsg); +} + /** * redis_sock_get */ @@ -251,16 +276,9 @@ redis_sock_get(zval *id, int no_throw) return NULL; } - if (redis_sock_server_open(redis_sock) < 0) { + if (UNEXPECTED(redis_sock_server_open(redis_sock) < 0)) { if (!no_throw) { - char *errmsg = NULL; - if (redis_sock->port < 0) { - spprintf(&errmsg, 0, "Redis server %s went away", ZSTR_VAL(redis_sock->host)); - } else { - spprintf(&errmsg, 0, "Redis server %s:%d went away", ZSTR_VAL(redis_sock->host), redis_sock->port); - } - REDIS_THROW_EXCEPTION(errmsg, 0); - efree(errmsg); + redis_sock_throw_exception(redis_sock); } return NULL; } @@ -760,17 +778,11 @@ PHP_METHOD(Redis, reset) } /* }}} */ -static void -redis_get_passthru(INTERNAL_FUNCTION_PARAMETERS) -{ - REDIS_PROCESS_KW_CMD("GET", redis_key_cmd, redis_string_response); -} - /* {{{ proto string Redis::get(string key) */ PHP_METHOD(Redis, get) { - redis_get_passthru(INTERNAL_FUNCTION_PARAM_PASSTHRU); + REDIS_PROCESS_KW_CMD("GET", redis_key_cmd, redis_string_response); } /* }}} */ @@ -778,14 +790,7 @@ PHP_METHOD(Redis, get) */ PHP_METHOD(Redis, getWithMeta) { - RedisSock *redis_sock; - if ((redis_sock = redis_sock_get_instance(getThis(), 0)) == NULL) { - RETURN_FALSE; - } - - REDIS_ENABLE_FLAG(redis_sock, PHPREDIS_WITH_METADATA); - redis_get_passthru(INTERNAL_FUNCTION_PARAM_PASSTHRU); - REDIS_DISABLE_FLAG(redis_sock, PHPREDIS_WITH_METADATA); + REDIS_PROCESS_KW_CMD("GET", redis_key_cmd, redis_bulk_withmeta_response); } /* }}} */ @@ -1873,6 +1878,51 @@ PHP_METHOD(Redis, hMset) } /* }}} */ +PHP_METHOD(Redis, hexpire) { + REDIS_PROCESS_KW_CMD("HEXPIRE", redis_hexpire_cmd, + redis_read_variant_reply); +} + +PHP_METHOD(Redis, hpexpire) { + REDIS_PROCESS_KW_CMD("HPEXPIRE", redis_hexpire_cmd, + redis_read_variant_reply); +} + +PHP_METHOD(Redis, hexpireat) { + REDIS_PROCESS_KW_CMD("HEXPIREAT", redis_hexpire_cmd, + redis_read_variant_reply); +} + +PHP_METHOD(Redis, hpexpireat) { + REDIS_PROCESS_KW_CMD("HPEXPIREAT", redis_hexpire_cmd, + redis_read_variant_reply); +} + +PHP_METHOD(Redis, httl) { + REDIS_PROCESS_KW_CMD("HTTL", redis_httl_cmd, + redis_read_variant_reply); +} + +PHP_METHOD(Redis, hpttl) { + REDIS_PROCESS_KW_CMD("HPTTL", redis_httl_cmd, + redis_read_variant_reply); +} + +PHP_METHOD(Redis, hexpiretime) { + REDIS_PROCESS_KW_CMD("HEXPIRETIME", redis_httl_cmd, + redis_read_variant_reply); +} + +PHP_METHOD(Redis, hpexpiretime) { + REDIS_PROCESS_KW_CMD("HPEXPIRETIME", redis_httl_cmd, + redis_read_variant_reply); +} + +PHP_METHOD(Redis, hpersist) { + REDIS_PROCESS_KW_CMD("HPERSIST", redis_httl_cmd, + redis_read_variant_reply); +} + /* {{{ proto bool Redis::hRandField(string key, [array $options]) */ PHP_METHOD(Redis, hRandField) { @@ -2126,7 +2176,7 @@ redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAMETERS, int num = atol(inbuf + 1); - if (num > 0 && redis_read_multibulk_recursive(redis_sock, num, 0, &z_ret) < 0) { + if (num > 0 && redis_read_multibulk_recursive(redis_sock, num, 0, &z_ret) != SUCCESS) { return FAILURE; } } diff --git a/redis.stub.php b/redis.stub.php index 8ace66a8c5..3f95468d7a 100644 --- a/redis.stub.php +++ b/redis.stub.php @@ -1733,7 +1733,7 @@ public function hGet(string $key, string $member): mixed; * Read every field and value from a hash. * * @param string $key The hash to query. - * @return Redis|array|false All fields and values or false if the key didn't exist. + * @return Redis|array|false All fields and values or false if the key didn't exist. * * @see https://redis.io/commands/hgetall * @@ -1913,6 +1913,121 @@ public function hStrLen(string $key, string $field): Redis|int|false; */ public function hVals(string $key): Redis|array|false; + /** + * Set the expiration on one or more fields in a hash. + * + * @param string $key The hash to update. + * @param int $ttl The time to live in seconds. + * @param array $fields The fields to set the expiration on. + * @param string|null $option An optional mode (NX, XX, ETC) + * @return Redis|array|false + * + * @see https://redis.io/commands/hexpire + */ + public function hexpire(string $key, int $ttl, array $fields, + ?string $mode = NULL): Redis|array|false; + + /** + * Set the expiration on one or more fields in a hash in milliseconds. + * + * @param string $key The hash to update. + * @param int $ttl The time to live in milliseconds. + * @param array $fields The fields to set the expiration on. + * @param string|null $option An optional mode (NX, XX, ETC) + * @return Redis|array|false + * + * @see https://redis.io/commands/hexpire + */ + public function hpexpire(string $key, int $ttl, array $fields, + ?string $mode = NULL): Redis|array|false; + + /** + * Set the expiration time on one or more fields of a hash. + * + * @param string $key The hash to update. + * @param int $time The time to live in seconds. + * @param array $fields The fields to set the expiration on. + * @param string|null $option An optional mode (NX, XX, ETC) + * @return Redis|array|false + * + * @see https://redis.io/commands/hexpire + */ + public function hexpireat(string $key, int $time, array $fields, + ?string $mode = NULL): Redis|array|false; + + /** + * Set the expiration time on one or more fields of a hash in milliseconds. + * + * @param string $key The hash to update. + * @param int $mstime The time to live in milliseconds. + * @param array $fields The fields to set the expiration on. + * @param string|null $option An optional mode (NX, XX, ETC) + * @return Redis|array|false + * + * @see https://redis.io/commands/hexpire + */ + public function hpexpireat(string $key, int $mstime, array $fields, + ?string $mode = NULL): Redis|array|false; + + /** + * Get the TTL of one or more fields in a hash + * + * @param string $key The hash to query. + * @param array $fields The fields to query. + * + * @return Redis|array|false + * + * @see https://redis.io/commands/httl + */ + public function httl(string $key, array $fields): Redis|array|false; + + /** + * Get the millisecond TTL of one or more fields in a hash + * + * @param string $key The hash to query. + * @param array $fields The fields to query. + * + * @return Redis|array|false + * + * @see https://redis.io/commands/hpttl + */ + public function hpttl(string $key, array $fields): Redis|array|false; + + /** + * Get the expiration time of one or more fields in a hash + * + * @param string $key The hash to query. + * @param array $fields The fields to query. + * + * @return Redis|array|false + * + * @see https://redis.io/commands/hexpiretime + */ + public function hexpiretime(string $key, array $fields): Redis|array|false; + + /** + * Get the expiration time in milliseconds of one or more fields in a hash + * + * @param string $key The hash to query. + * @param array $fields The fields to query. + * + * @return Redis|array|false + * + * @see https://redis.io/commands/hpexpiretime + */ + public function hpexpiretime(string $key, array $fields): Redis|array|false; + + /** + * Persist one or more hash fields + * + * @param string $key The hash to query. + * @param array $fields The fields to query. + * + * @return Redis|array|false + * + * @see https://redis.io/commands/hpersist + */ + public function hpersist(string $key, array $fields): Redis|array|false; /** * Iterate over the fields and values of a hash in an incremental fashion. diff --git a/redis_arginfo.h b/redis_arginfo.h index 08a2308ffe..32c2754da4 100644 --- a/redis_arginfo.h +++ b/redis_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 805a66c17b7c9972c73a979bdd67f98f7c1f6c74 */ + * Stub hash: c6205649cd23ff2b9fcc63a034b601ee566ef236 */ 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") @@ -464,6 +464,39 @@ ZEND_END_ARG_INFO() #define arginfo_class_Redis_hVals arginfo_class_Redis_getWithMeta +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_hexpire, 0, 3, Redis, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, ttl, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, fields, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_STRING, 1, "NULL") +ZEND_END_ARG_INFO() + +#define arginfo_class_Redis_hpexpire arginfo_class_Redis_hexpire + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_hexpireat, 0, 3, Redis, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, time, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, fields, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_STRING, 1, "NULL") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_hpexpireat, 0, 3, Redis, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, mstime, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, fields, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_STRING, 1, "NULL") +ZEND_END_ARG_INFO() + +#define arginfo_class_Redis_httl arginfo_class_Redis_hMget + +#define arginfo_class_Redis_hpttl arginfo_class_Redis_hMget + +#define arginfo_class_Redis_hexpiretime arginfo_class_Redis_hMget + +#define arginfo_class_Redis_hpexpiretime arginfo_class_Redis_hMget + +#define arginfo_class_Redis_hpersist arginfo_class_Redis_hMget + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_hscan, 0, 2, Redis, MAY_BE_ARRAY|MAY_BE_BOOL) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_ARG_TYPE_MASK(1, iterator, MAY_BE_NULL|MAY_BE_LONG|MAY_BE_STRING, NULL) @@ -1292,6 +1325,15 @@ ZEND_METHOD(Redis, hSet); ZEND_METHOD(Redis, hSetNx); ZEND_METHOD(Redis, hStrLen); ZEND_METHOD(Redis, hVals); +ZEND_METHOD(Redis, hexpire); +ZEND_METHOD(Redis, hpexpire); +ZEND_METHOD(Redis, hexpireat); +ZEND_METHOD(Redis, hpexpireat); +ZEND_METHOD(Redis, httl); +ZEND_METHOD(Redis, hpttl); +ZEND_METHOD(Redis, hexpiretime); +ZEND_METHOD(Redis, hpexpiretime); +ZEND_METHOD(Redis, hpersist); ZEND_METHOD(Redis, hscan); ZEND_METHOD(Redis, expiremember); ZEND_METHOD(Redis, expirememberat); @@ -1553,6 +1595,15 @@ static const zend_function_entry class_Redis_methods[] = { ZEND_ME(Redis, hSetNx, arginfo_class_Redis_hSetNx, ZEND_ACC_PUBLIC) ZEND_ME(Redis, hStrLen, arginfo_class_Redis_hStrLen, ZEND_ACC_PUBLIC) ZEND_ME(Redis, hVals, arginfo_class_Redis_hVals, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hexpire, arginfo_class_Redis_hexpire, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hpexpire, arginfo_class_Redis_hpexpire, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hexpireat, arginfo_class_Redis_hexpireat, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hpexpireat, arginfo_class_Redis_hpexpireat, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, httl, arginfo_class_Redis_httl, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hpttl, arginfo_class_Redis_hpttl, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hexpiretime, arginfo_class_Redis_hexpiretime, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hpexpiretime, arginfo_class_Redis_hpexpiretime, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hpersist, arginfo_class_Redis_hpersist, ZEND_ACC_PUBLIC) ZEND_ME(Redis, hscan, arginfo_class_Redis_hscan, ZEND_ACC_PUBLIC) ZEND_ME(Redis, expiremember, arginfo_class_Redis_expiremember, ZEND_ACC_PUBLIC) ZEND_ME(Redis, expirememberat, arginfo_class_Redis_expirememberat, ZEND_ACC_PUBLIC) diff --git a/redis_array_impl.c b/redis_array_impl.c index 78b6d16789..a8d06875ed 100644 --- a/redis_array_impl.c +++ b/redis_array_impl.c @@ -91,7 +91,7 @@ ra_init_function_table(RedisArray *ra) zend_hash_init(ra->pure_cmds, 0, NULL, NULL, 0); #define ra_add_pure_cmd(cmd) \ - zend_hash_str_update_ptr(ra->pure_cmds, cmd, sizeof(cmd) - 1, NULL); + zend_hash_str_add_empty_element(ra->pure_cmds, cmd, sizeof(cmd) - 1); ra_add_pure_cmd("EXISTS"); ra_add_pure_cmd("GET"); diff --git a/redis_cluster.c b/redis_cluster.c index a412130df9..7b63696298 100644 --- a/redis_cluster.c +++ b/redis_cluster.c @@ -275,15 +275,9 @@ PHP_METHOD(RedisCluster, close) { RETURN_TRUE; } -static void -cluster_get_passthru(INTERNAL_FUNCTION_PARAMETERS) -{ - CLUSTER_PROCESS_KW_CMD("GET", redis_key_cmd, cluster_bulk_resp, 1); -} - /* {{{ proto string RedisCluster::get(string key) */ PHP_METHOD(RedisCluster, get) { - cluster_get_passthru(INTERNAL_FUNCTION_PARAM_PASSTHRU); + CLUSTER_PROCESS_KW_CMD("GET", redis_key_cmd, cluster_bulk_resp, 1); } /* }}} */ @@ -295,10 +289,7 @@ PHP_METHOD(RedisCluster, getdel) { /* {{{ proto array|false RedisCluster::getWithMeta(string key) */ PHP_METHOD(RedisCluster, getWithMeta) { - redisCluster *c = GET_CONTEXT(); - REDIS_ENABLE_FLAG(c->flags, PHPREDIS_WITH_METADATA); - cluster_get_passthru(INTERNAL_FUNCTION_PARAM_PASSTHRU); - REDIS_DISABLE_FLAG(c->flags, PHPREDIS_WITH_METADATA); + CLUSTER_PROCESS_KW_CMD("GET", redis_key_cmd, cluster_bulk_withmeta_resp, 1); } /* }}} */ @@ -1212,6 +1203,49 @@ PHP_METHOD(RedisCluster, hmset) { } /* }}} */ +PHP_METHOD(RedisCluster, hexpire) { + CLUSTER_PROCESS_KW_CMD("HEXPIRE", + redis_hexpire_cmd, cluster_variant_resp, 0); +} + +PHP_METHOD(RedisCluster, hpexpire) { + CLUSTER_PROCESS_KW_CMD("HPEXPIRE", + redis_hexpire_cmd, cluster_variant_resp, 0); +} + +PHP_METHOD(RedisCluster, hexpireat) { + CLUSTER_PROCESS_KW_CMD("HEXPIREAT", + redis_hexpire_cmd, cluster_variant_resp, 0); +} + +PHP_METHOD(RedisCluster, hpexpireat) { + CLUSTER_PROCESS_KW_CMD("HPEXPIREAT", + redis_hexpire_cmd, cluster_variant_resp, 0); +} + +PHP_METHOD(RedisCluster, httl) { + CLUSTER_PROCESS_KW_CMD("HTTL", redis_httl_cmd, cluster_variant_resp, 1); +} + +PHP_METHOD(RedisCluster, hpttl) { + CLUSTER_PROCESS_KW_CMD("HPTTL", redis_httl_cmd, cluster_variant_resp, 1); +} + + +PHP_METHOD(RedisCluster, hexpiretime) { + CLUSTER_PROCESS_KW_CMD("HEXPIRETIME", redis_httl_cmd, + cluster_variant_resp, 1); +} + +PHP_METHOD(RedisCluster, hpexpiretime) { + CLUSTER_PROCESS_KW_CMD("HPEXPIRETIME", redis_httl_cmd, + cluster_variant_resp, 1); +} + +PHP_METHOD(RedisCluster, hpersist) { + CLUSTER_PROCESS_KW_CMD("HPERSIST", redis_httl_cmd, cluster_variant_resp, 0); +} + /* {{{ proto bool RedisCluster::hrandfield(string key, [array $options]) */ PHP_METHOD(RedisCluster, hrandfield) { CLUSTER_PROCESS_CMD(hrandfield, cluster_hrandfield_resp, 1); diff --git a/redis_cluster.stub.php b/redis_cluster.stub.php index 58cced5777..05a6df7115 100644 --- a/redis_cluster.stub.php +++ b/redis_cluster.stub.php @@ -535,6 +535,55 @@ public function hsetnx(string $key, string $member, mixed $value): RedisCluster| */ public function hstrlen(string $key, string $field): RedisCluster|int|false; + /** + * @see Redis::hexpire + */ + public function hexpire(string $key, int $ttl, array $fields, + ?string $mode = NULL): RedisCluster|array|false; + + /** + * @see Redis::hpexpire + */ + public function hpexpire(string $key, int $ttl, array $fields, + ?string $mode = NULL): RedisCluster|array|false; + + /** + * @see Redis::hexpireat + */ + public function hexpireat(string $key, int $time, array $fields, + ?string $mode = NULL): RedisCluster|array|false; + + /** + * @see Redis::hpexpireat + */ + public function hpexpireat(string $key, int $mstime, array $fields, + ?string $mode = NULL): RedisCluster|array|false; + + /** + * @see Redis::httl + */ + public function httl(string $key, array $fields): RedisCluster|array|false; + + /** + * @see Redis::hpttl + */ + public function hpttl(string $key, array $fields): RedisCluster|array|false; + + /** + * @see Redis::hexpiretime + */ + public function hexpiretime(string $key, array $fields): RedisCluster|array|false; + + /** + * @see Redis::hpexpiretime + */ + public function hpexpiretime(string $key, array $fields): RedisCluster|array|false; + + /** + * @see Redis::hpexpiretime + */ + public function hpersist(string $key, array $fields): RedisCluster|array|false; + /** * @see Redis::hvals */ diff --git a/redis_cluster_arginfo.h b/redis_cluster_arginfo.h index b3fb58475a..4fea76b2f4 100644 --- a/redis_cluster_arginfo.h +++ b/redis_cluster_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 43a43fa735ced4b48a361078ac8a10fb62cb1244 */ + * Stub hash: 5788cd1d12611ef1ff5747efe07b99f66f07fa05 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 1) @@ -455,6 +455,42 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_RedisCluster_hstrlen, ZEND_ARG_TYPE_INFO(0, field, IS_STRING, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_RedisCluster_hexpire, 0, 3, RedisCluster, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, ttl, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, fields, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_STRING, 1, "NULL") +ZEND_END_ARG_INFO() + +#define arginfo_class_RedisCluster_hpexpire arginfo_class_RedisCluster_hexpire + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_RedisCluster_hexpireat, 0, 3, RedisCluster, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, time, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, fields, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_STRING, 1, "NULL") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_RedisCluster_hpexpireat, 0, 3, RedisCluster, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, mstime, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, fields, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_STRING, 1, "NULL") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_RedisCluster_httl, 0, 2, RedisCluster, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, fields, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_RedisCluster_hpttl arginfo_class_RedisCluster_httl + +#define arginfo_class_RedisCluster_hexpiretime arginfo_class_RedisCluster_httl + +#define arginfo_class_RedisCluster_hpexpiretime arginfo_class_RedisCluster_httl + +#define arginfo_class_RedisCluster_hpersist arginfo_class_RedisCluster_httl + #define arginfo_class_RedisCluster_hvals arginfo_class_RedisCluster_getWithMeta #define arginfo_class_RedisCluster_incr arginfo_class_RedisCluster_decr @@ -1160,6 +1196,15 @@ ZEND_METHOD(RedisCluster, hrandfield); ZEND_METHOD(RedisCluster, hset); ZEND_METHOD(RedisCluster, hsetnx); ZEND_METHOD(RedisCluster, hstrlen); +ZEND_METHOD(RedisCluster, hexpire); +ZEND_METHOD(RedisCluster, hpexpire); +ZEND_METHOD(RedisCluster, hexpireat); +ZEND_METHOD(RedisCluster, hpexpireat); +ZEND_METHOD(RedisCluster, httl); +ZEND_METHOD(RedisCluster, hpttl); +ZEND_METHOD(RedisCluster, hexpiretime); +ZEND_METHOD(RedisCluster, hpexpiretime); +ZEND_METHOD(RedisCluster, hpersist); ZEND_METHOD(RedisCluster, hvals); ZEND_METHOD(RedisCluster, incr); ZEND_METHOD(RedisCluster, incrby); @@ -1391,6 +1436,15 @@ static const zend_function_entry class_RedisCluster_methods[] = { ZEND_ME(RedisCluster, hset, arginfo_class_RedisCluster_hset, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, hsetnx, arginfo_class_RedisCluster_hsetnx, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, hstrlen, arginfo_class_RedisCluster_hstrlen, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hexpire, arginfo_class_RedisCluster_hexpire, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hpexpire, arginfo_class_RedisCluster_hpexpire, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hexpireat, arginfo_class_RedisCluster_hexpireat, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hpexpireat, arginfo_class_RedisCluster_hpexpireat, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, httl, arginfo_class_RedisCluster_httl, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hpttl, arginfo_class_RedisCluster_hpttl, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hexpiretime, arginfo_class_RedisCluster_hexpiretime, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hpexpiretime, arginfo_class_RedisCluster_hpexpiretime, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hpersist, arginfo_class_RedisCluster_hpersist, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, hvals, arginfo_class_RedisCluster_hvals, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, incr, arginfo_class_RedisCluster_incr, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, incrby, arginfo_class_RedisCluster_incrby, ZEND_ACC_PUBLIC) diff --git a/redis_cluster_legacy_arginfo.h b/redis_cluster_legacy_arginfo.h index d117db522a..e1a18b16df 100644 --- a/redis_cluster_legacy_arginfo.h +++ b/redis_cluster_legacy_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 43a43fa735ced4b48a361078ac8a10fb62cb1244 */ + * Stub hash: 5788cd1d12611ef1ff5747efe07b99f66f07fa05 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster___construct, 0, 0, 1) ZEND_ARG_INFO(0, name) @@ -396,6 +396,42 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_hstrlen, 0, 0, 2) ZEND_ARG_INFO(0, field) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_hexpire, 0, 0, 3) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, ttl) + ZEND_ARG_INFO(0, fields) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() + +#define arginfo_class_RedisCluster_hpexpire arginfo_class_RedisCluster_hexpire + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_hexpireat, 0, 0, 3) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, time) + ZEND_ARG_INFO(0, fields) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_hpexpireat, 0, 0, 3) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, mstime) + ZEND_ARG_INFO(0, fields) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_httl, 0, 0, 2) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, fields) +ZEND_END_ARG_INFO() + +#define arginfo_class_RedisCluster_hpttl arginfo_class_RedisCluster_httl + +#define arginfo_class_RedisCluster_hexpiretime arginfo_class_RedisCluster_httl + +#define arginfo_class_RedisCluster_hpexpiretime arginfo_class_RedisCluster_httl + +#define arginfo_class_RedisCluster_hpersist arginfo_class_RedisCluster_httl + #define arginfo_class_RedisCluster_hvals arginfo_class_RedisCluster__prefix #define arginfo_class_RedisCluster_incr arginfo_class_RedisCluster_decr @@ -1002,6 +1038,15 @@ ZEND_METHOD(RedisCluster, hrandfield); ZEND_METHOD(RedisCluster, hset); ZEND_METHOD(RedisCluster, hsetnx); ZEND_METHOD(RedisCluster, hstrlen); +ZEND_METHOD(RedisCluster, hexpire); +ZEND_METHOD(RedisCluster, hpexpire); +ZEND_METHOD(RedisCluster, hexpireat); +ZEND_METHOD(RedisCluster, hpexpireat); +ZEND_METHOD(RedisCluster, httl); +ZEND_METHOD(RedisCluster, hpttl); +ZEND_METHOD(RedisCluster, hexpiretime); +ZEND_METHOD(RedisCluster, hpexpiretime); +ZEND_METHOD(RedisCluster, hpersist); ZEND_METHOD(RedisCluster, hvals); ZEND_METHOD(RedisCluster, incr); ZEND_METHOD(RedisCluster, incrby); @@ -1233,6 +1278,15 @@ static const zend_function_entry class_RedisCluster_methods[] = { ZEND_ME(RedisCluster, hset, arginfo_class_RedisCluster_hset, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, hsetnx, arginfo_class_RedisCluster_hsetnx, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, hstrlen, arginfo_class_RedisCluster_hstrlen, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hexpire, arginfo_class_RedisCluster_hexpire, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hpexpire, arginfo_class_RedisCluster_hpexpire, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hexpireat, arginfo_class_RedisCluster_hexpireat, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hpexpireat, arginfo_class_RedisCluster_hpexpireat, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, httl, arginfo_class_RedisCluster_httl, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hpttl, arginfo_class_RedisCluster_hpttl, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hexpiretime, arginfo_class_RedisCluster_hexpiretime, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hpexpiretime, arginfo_class_RedisCluster_hpexpiretime, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, hpersist, arginfo_class_RedisCluster_hpersist, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, hvals, arginfo_class_RedisCluster_hvals, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, incr, arginfo_class_RedisCluster_incr, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, incrby, arginfo_class_RedisCluster_incrby, ZEND_ACC_PUBLIC) diff --git a/redis_commands.c b/redis_commands.c index 1d2c23360f..f473da4e33 100644 --- a/redis_commands.c +++ b/redis_commands.c @@ -2375,7 +2375,7 @@ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, } /* Calculate argc based on options set */ - int argc = 2 + (ifeq ? 2 : 0) + (exp_type ? 2 : 0) + (set_type != NULL) + + int argc = 2 + (ifeq ? 2 : 0) + (exp_type ? 2 : 0) + (set_type != NULL) + (keep_ttl != 0) + get; /* Initial SET */ @@ -4634,6 +4634,92 @@ redis_geosearchstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, Starting with Redis version 6.0.0: Added the AUTH2 option. */ +int redis_httl_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, + char **cmd, int *cmd_len, short *slot, void **ctx) +{ + smart_string cmdstr = {0}; + zend_string *key, *field, *tmp; + HashTable *fields; + int argc; + zval *zv; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(key) + Z_PARAM_ARRAY_HT(fields) + ZEND_PARSE_PARAMETERS_END_EX(return FAILURE); + + if (zend_hash_num_elements(fields) < 1) { + php_error_docref(NULL, E_WARNING, "Must pass at least one field"); + return FAILURE; + } + + // 3 because FIELDS + argc = 3 + zend_hash_num_elements(fields); + redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw)); + + redis_cmd_append_sstr_key_zstr(&cmdstr, key, redis_sock, slot); + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "FIELDS"); + redis_cmd_append_sstr_long(&cmdstr, zend_hash_num_elements(fields)); + + ZEND_HASH_FOREACH_VAL(fields, zv) + field = zval_get_tmp_string(zv, &tmp); + redis_cmd_append_sstr_zstr(&cmdstr, field); + zend_tmp_string_release(tmp); + ZEND_HASH_FOREACH_END(); + + *cmd = cmdstr.c; + *cmd_len = cmdstr.len; + + return SUCCESS; +} + +int redis_hexpire_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + char *kw, char **cmd, int *cmd_len, short *slot, + void **ctx) +{ + zend_string *key, *option = NULL, *tmp, *field; + smart_string cmdstr = {0}; + HashTable *fields; + zend_long ttl; + zval *zv; + int argc; + + ZEND_PARSE_PARAMETERS_START(3, 4) + Z_PARAM_STR(key) + Z_PARAM_LONG(ttl) + Z_PARAM_ARRAY_HT(fields) + Z_PARAM_OPTIONAL + Z_PARAM_STR(option) + ZEND_PARSE_PARAMETERS_END_EX(return FAILURE); + + if (zend_hash_num_elements(fields) < 1) { + php_error_docref(NULL, E_WARNING, "Must pass at least one field"); + return FAILURE; + } + + // 4 because FIELDS + argc = 4 + zend_hash_num_elements(fields) + (option ? 1 : 0); + redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw)); + + redis_cmd_append_sstr_key_zstr(&cmdstr, key, redis_sock, slot); + redis_cmd_append_sstr_long(&cmdstr, ttl); + if (option) redis_cmd_append_sstr_zstr(&cmdstr, option); + + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "FIELDS"); + redis_cmd_append_sstr_long(&cmdstr, zend_hash_num_elements(fields)); + + ZEND_HASH_FOREACH_VAL(fields, zv) + field = zval_get_tmp_string(zv, &tmp); + redis_cmd_append_sstr_zstr(&cmdstr, field); + zend_tmp_string_release(tmp); + ZEND_HASH_FOREACH_END(); + + *cmd = cmdstr.c; + *cmd_len = cmdstr.len; + + return SUCCESS; +} + /* MIGRATE */ int redis_migrate_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) @@ -6465,8 +6551,6 @@ void redis_unpack_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) { RETURN_FALSE; } - if (redis_unpack(redis_sock, ZSTR_VAL(str), ZSTR_LEN(str), return_value) == 0) { - RETURN_STR_COPY(str); - } + redis_unpack(redis_sock, ZSTR_VAL(str), ZSTR_LEN(str), return_value); } /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */ diff --git a/redis_commands.h b/redis_commands.h index b0c5895c4f..6b52fee489 100644 --- a/redis_commands.h +++ b/redis_commands.h @@ -356,6 +356,12 @@ int redis_expiremember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, int redis_expirememberat_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); +int redis_hexpire_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); + +int redis_httl_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, + char **cmd, int *cmd_len, short *slot, void **ctx); + int redis_lmove_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); diff --git a/redis_legacy_arginfo.h b/redis_legacy_arginfo.h index 6bfc3a39ab..27acccc659 100644 --- a/redis_legacy_arginfo.h +++ b/redis_legacy_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 805a66c17b7c9972c73a979bdd67f98f7c1f6c74 */ + * Stub hash: c6205649cd23ff2b9fcc63a034b601ee566ef236 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0) ZEND_ARG_INFO(0, options) @@ -412,6 +412,39 @@ ZEND_END_ARG_INFO() #define arginfo_class_Redis_hVals arginfo_class_Redis__prefix +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_hexpire, 0, 0, 3) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, ttl) + ZEND_ARG_INFO(0, fields) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() + +#define arginfo_class_Redis_hpexpire arginfo_class_Redis_hexpire + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_hexpireat, 0, 0, 3) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, time) + ZEND_ARG_INFO(0, fields) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_hpexpireat, 0, 0, 3) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, mstime) + ZEND_ARG_INFO(0, fields) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() + +#define arginfo_class_Redis_httl arginfo_class_Redis_hMget + +#define arginfo_class_Redis_hpttl arginfo_class_Redis_hMget + +#define arginfo_class_Redis_hexpiretime arginfo_class_Redis_hMget + +#define arginfo_class_Redis_hpexpiretime arginfo_class_Redis_hMget + +#define arginfo_class_Redis_hpersist arginfo_class_Redis_hMget + ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_hscan, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(1, iterator) @@ -1134,6 +1167,15 @@ ZEND_METHOD(Redis, hSet); ZEND_METHOD(Redis, hSetNx); ZEND_METHOD(Redis, hStrLen); ZEND_METHOD(Redis, hVals); +ZEND_METHOD(Redis, hexpire); +ZEND_METHOD(Redis, hpexpire); +ZEND_METHOD(Redis, hexpireat); +ZEND_METHOD(Redis, hpexpireat); +ZEND_METHOD(Redis, httl); +ZEND_METHOD(Redis, hpttl); +ZEND_METHOD(Redis, hexpiretime); +ZEND_METHOD(Redis, hpexpiretime); +ZEND_METHOD(Redis, hpersist); ZEND_METHOD(Redis, hscan); ZEND_METHOD(Redis, expiremember); ZEND_METHOD(Redis, expirememberat); @@ -1395,6 +1437,15 @@ static const zend_function_entry class_Redis_methods[] = { ZEND_ME(Redis, hSetNx, arginfo_class_Redis_hSetNx, ZEND_ACC_PUBLIC) ZEND_ME(Redis, hStrLen, arginfo_class_Redis_hStrLen, ZEND_ACC_PUBLIC) ZEND_ME(Redis, hVals, arginfo_class_Redis_hVals, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hexpire, arginfo_class_Redis_hexpire, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hpexpire, arginfo_class_Redis_hpexpire, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hexpireat, arginfo_class_Redis_hexpireat, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hpexpireat, arginfo_class_Redis_hpexpireat, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, httl, arginfo_class_Redis_httl, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hpttl, arginfo_class_Redis_hpttl, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hexpiretime, arginfo_class_Redis_hexpiretime, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hpexpiretime, arginfo_class_Redis_hpexpiretime, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, hpersist, arginfo_class_Redis_hpersist, ZEND_ACC_PUBLIC) ZEND_ME(Redis, hscan, arginfo_class_Redis_hscan, ZEND_ACC_PUBLIC) ZEND_ME(Redis, expiremember, arginfo_class_Redis_expiremember, ZEND_ACC_PUBLIC) ZEND_ME(Redis, expirememberat, arginfo_class_Redis_expirememberat, ZEND_ACC_PUBLIC) diff --git a/tests/RedisClusterTest.php b/tests/RedisClusterTest.php index 0b1636f830..a9a70e2e39 100644 --- a/tests/RedisClusterTest.php +++ b/tests/RedisClusterTest.php @@ -48,6 +48,7 @@ public function testTlsConnect() { $this->markTestSkipped(); } public function testReset() { $this->markTestSkipped(); } public function testInvalidAuthArgs() { $this->markTestSkipped(); } public function testScanErrors() { $this->markTestSkipped(); } + public function testConnectDatabaseSelect() { $this->markTestSkipped(); } /* These 'directed node' commands work differently in RedisCluster */ public function testConfig() { $this->markTestSkipped(); } @@ -138,14 +139,86 @@ public function setUp() { $this->is_valkey = $this->detectValkey($info); } + private function findCliExe() { + foreach (['redis-cli', 'valkey-cli'] as $candidate) { + $path = trim(shell_exec("command -v $candidate 2>/dev/null")); + if (is_executable($path)) { + return $path; + } + } + + return NULL; + } + + private function getServerReply($host, $port, $cmd) { + $cli = $this->findCliExe(); + if ( ! $cli) { + return '(no redis-cli or valkey-cli found)'; + } + + $args = [$cli, '-h', $host, '-p', $port]; + + $this->getAuthParts($user, $pass); + + if ($user) $args = array_merge($args, ['--user', $user]); + if ($pass) $args = array_merge($args, ['-a', $pass]); + + $resp = shell_exec(implode(' ', $args) . ' ' . $cmd . ' 2>/dev/null'); + + return is_string($resp) ? trim($resp) : $resp; + } + + /* Try to gat a new RedisCluster instance. The strange logic is an attempt + to solve a problem where this sometimes fails but only ever on GitHub + runners. If we're not on a runner we just get a new instance. Otherwise + we allow for two tries to get the instance. */ + private function getNewInstance() { + if (getenv('GITHUB_ACTIONS') === 'true') { + try { + return new RedisCluster(NULL, self::$seeds, 30, 30, true, + $this->getAuth()); + } catch (Exception $ex) { + TestSuite::errorMessage("Failed to connect: %s", $ex->getMessage()); + } + } + + return new RedisCluster(NULL, self::$seeds, 30, 30, true, $this->getAuth()); + } + /* Override newInstance as we want a RedisCluster object */ protected function newInstance() { try { - return new RedisCluster(NULL, self::$seeds, 30, 30, true, $this->getAuth()); + return $this->getNewInstance(); } catch (Exception $ex) { - TestSuite::errorMessage("Fatal error: %s\n", $ex->getMessage()); - TestSuite::errorMessage("Seeds: %s\n", implode(' ', self::$seeds)); - TestSuite::errorMessage("Seed source: %s\n", self::$seed_source); + TestSuite::errorMessage(""); + TestSuite::errorMessage("Fatal error: %s", $ex->getMessage()); + TestSuite::errorMessage("Seeds: %s", implode(' ', self::$seeds)); + TestSuite::errorMessage("Seed source: %s", self::$seed_source); + TestSuite::errorMessage(""); + + TestSuite::errorMessage("Backtrace:"); + foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $i => $frame) { + $file = isset($frame['file']) ? basename($frame['file']) : '[internal]'; + $line = $frame['line'] ?? '?'; + $func = $frame['function'] ?? 'unknown'; + TestSuite::errorMessage(" %s:%d [%s]", $file, $line, $func); + } + + TestSuite::errorMessage("\nServer responses:"); + + /* See if we can shed some light on whether Redis is available */ + foreach (self::$seeds as $seed) { + list($host, $port) = explode(':', $seed); + + $st = microtime(true); + $reply = $this->getServerReply($host, $port, 'PING'); + $et = microtime(true); + + TestSuite::errorMessage(" [%s:%d] PING -> %s (%.4f)", $host, + $port, var_export($reply, true), + $et - $st); + } + exit(1); } } @@ -250,53 +323,6 @@ public function testClient() { $this->assertTrue($this->redis->client($key, 'kill', $addr)); } - public function testGetWithMeta() { - $this->redis->del('key'); - $this->assertFalse($this->redis->get('key')); - - $result = $this->redis->getWithMeta('key'); - $this->assertIsArray($result, 2); - $this->assertArrayKeyEquals($result, 0, false); - $this->assertArrayKey($result, 1, function ($metadata) { - $this->assertIsArray($metadata); - $this->assertArrayKeyEquals($metadata, 'length', -1); - return true; - }); - - $batch = $this->redis->multi() - ->set('key', 'value') - ->getWithMeta('key') - ->exec(); - $this->assertIsArray($batch, 2); - $this->assertArrayKeyEquals($batch, 0, true); - $this->assertArrayKey($batch, 1, function ($result) { - $this->assertIsArray($result, 2); - $this->assertArrayKeyEquals($result, 0, 'value'); - $this->assertArrayKey($result, 1, function ($metadata) { - $this->assertIsArray($metadata); - $this->assertArrayKeyEquals($metadata, 'length', strlen('value')); - return true; - }); - return true; - }); - - $serializer = $this->redis->getOption(Redis::OPT_SERIALIZER); - $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); - $this->assertTrue($this->redis->set('key', false)); - - $result = $this->redis->getWithMeta('key'); - $this->assertIsArray($result, 2); - $this->assertArrayKeyEquals($result, 0, false); - $this->assertArrayKey($result, 1, function ($metadata) { - $this->assertIsArray($metadata); - $this->assertArrayKeyEquals($metadata, 'length', strlen(serialize(false))); - return true; - }); - - $this->assertFalse($this->redis->get('key')); - $this->redis->setOption(Redis::OPT_SERIALIZER, $serializer); - } - public function testTime() { [$sec, $usec] = $this->redis->time(uniqid()); $this->assertEquals(strval(intval($sec)), strval($sec)); diff --git a/tests/RedisTest.php b/tests/RedisTest.php index cb69686671..783d23a9da 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -2457,7 +2457,7 @@ public function testInfo() { $this->assertTrue(is_array($res) && isset($res['redis_version']) && isset($res['used_memory'])); } - private function execHello() { + protected function execHello() { $zipped = []; $result = $this->redis->rawCommand('HELLO'); @@ -2476,6 +2476,7 @@ public function testServerInfo() { $this->markTestSkipped(); $hello = $this->execHello(); + if ( ! $this->assertArrayKey($hello, 'server') || ! $this->assertArrayKey($hello, 'version')) { @@ -2486,6 +2487,7 @@ public function testServerInfo() { $this->assertEquals($hello['version'], $this->redis->serverVersion()); $info = $this->redis->info(); + $cmd1 = $info['total_commands_processed']; /* Shouldn't hit the server */ @@ -5869,22 +5871,24 @@ public function testGetWithMeta() { return true; }); - $batch = $this->redis->pipeline() - ->get('key') - ->getWithMeta('key') - ->exec(); - $this->assertIsArray($batch, 2); - $this->assertArrayKeyEquals($batch, 0, false); - $this->assertArrayKey($batch, 1, function ($result) { - $this->assertIsArray($result, 2); - $this->assertArrayKeyEquals($result, 0, false); - $this->assertArrayKey($result, 1, function ($metadata) { - $this->assertIsArray($metadata); - $this->assertArrayKeyEquals($metadata, 'length', -1); + if ($this->havePipeline()) { + $batch = $this->redis->pipeline() + ->get('key') + ->getWithMeta('key') + ->exec(); + $this->assertIsArray($batch, 2); + $this->assertArrayKeyEquals($batch, 0, false); + $this->assertArrayKey($batch, 1, function ($result) { + $this->assertIsArray($result, 2); + $this->assertArrayKeyEquals($result, 0, false); + $this->assertArrayKey($result, 1, function ($metadata) { + $this->assertIsArray($metadata); + $this->assertArrayKeyEquals($metadata, 'length', -1); + return true; + }); return true; }); - return true; - }); + } $batch = $this->redis->multi() ->set('key', 'value') @@ -6185,6 +6189,7 @@ public function testScan() { // Use a unique ID so we can find our type keys $id = uniqid(); + $keys = []; // Create some simple keys and lists for ($i = 0; $i < 3; $i++) { $simple = "simple:{$id}:$i"; @@ -6283,6 +6288,68 @@ public function testBackoffOptions() { } } + public function testHashExpiration() { + if ( ! $this->minVersionCheck('7.4.0')) + $this->markTestSkipped(); + + $hexpire_cmds = [ + 'hexpire' => 10, + 'hpexpire' => 10000, + 'hexpireat' => time() + 10, + 'hpexpireat' => time() * 1000 + 10000, + ]; + + $httl_cmds = ['httl', 'hpttl', 'hexpiretime', 'hpexpiretime']; + + $hash = ['Picard' => 'Enterprise', 'Sisko' => 'Defiant']; + $keys = array_keys($hash); + + foreach ($hexpire_cmds as $exp_cmd => $ttl) { + $this->redis->del('hash'); + $this->redis->hmset('hash', $hash); + + /* Set a TTL on one existing and one non-existing field */ + $res = $this->redis->{$exp_cmd}('hash', $ttl, ['Picard', 'nofield']); + + $this->assertEquals($res, [1, -2]); + + foreach ($httl_cmds as $ttl_cmd) { + $res = $this->redis->{$ttl_cmd}('hash', $keys); + $this->assertIsArray($res); + $this->assertEquals(count($keys), count($res)); + + /* Picard: has an expiry (>0), Siskto does not (<0) */ + $this->assertTrue($res[0] > 0); + $this->assertTrue($res[1] < 0); + } + + $this->redis->del('m'); + $this->redis->hmset('m', ['F' => 'V']); + + // NX - Only set expiry if it doesn't have one + foreach ([[1], [0]] as $expected) { + $res = $this->redis->{$exp_cmd}('m', $ttl, ['F'], 'NX'); + $this->assertEquals($expected, $res); + } + + // XX - Set if it has one + $res = $this->redis->{$exp_cmd}('m', $ttl, ['F'], 'XX'); + $this->assertEquals([1], $res); + $this->redis->hpersist('m', ['F']); + $res = $this->redis->{$exp_cmd}('m', $ttl, ['F'], 'XX'); + $this->assertEquals([0], $res); + + // GT - should set if the new expiration is larger + $res = $this->redis->{$exp_cmd}('m', $ttl, ['F']); + $res = $this->redis->{$exp_cmd}('m', $ttl + 100, ['F'], 'GT'); + $this->assertEquals([1], $res); + + // LT - should not set if the new expiration is smaller + $res = $this->redis->{$exp_cmd}('m', intval($ttl / 2), ['F'], 'LT'); + $this->assertTrue(is_array($res) && $res[0] > 0); + } + } + public function testHScan() { if (version_compare($this->version, '2.8.0') < 0) $this->markTestSkipped(); @@ -7835,6 +7902,27 @@ public function testMultipleConnect() { } } + public function testConnectDatabaseSelect() { + $options = [ + 'host' => $this->getHost(), + 'port' => $this->getPort(), + 'database' => 2, + ]; + + if ($this->getAuth()) { + $options['auth'] = $this->getAuth(); + } + + $redis = new Redis($options); + $this->assertEquals(2, $redis->getDBNum()); + $this->assertEquals(2, $redis->client('info')['db']); + + $this->assertTrue($redis->select(1)); + + $this->assertEquals(1, $redis->getDBNum()); + $this->assertEquals(1, $redis->client('info')['db']); + } + public function testConnectException() { $host = 'github.com'; if (gethostbyname($host) === $host) diff --git a/tests/SessionHelpers.php b/tests/SessionHelpers.php index 90ae73beb6..c2fa12056f 100644 --- a/tests/SessionHelpers.php +++ b/tests/SessionHelpers.php @@ -80,7 +80,7 @@ class Runner { ]; private $prefix = NULL; - private $output_file; + private $output_file = NULL; private $exit_code = -1; private $cmd = NULL; private $pid; @@ -90,6 +90,12 @@ public function __construct() { $this->args['id'] = $this->createId(); } + public function __destruct() { + if ($this->output_file) { + unlink($this->output_file); + } + } + public function getExitCode(): int { return $this->exit_code; } @@ -183,7 +189,6 @@ private function createId(): string { } private function getTmpFileName() { - return '/tmp/sessiontmp.txt'; return tempnam(sys_get_temp_dir(), 'session'); }