From 300c5fb218ebb55fb6eca4de91756a91e57912ea Mon Sep 17 00:00:00 2001 From: michael-grunder Date: Fri, 21 Mar 2025 11:05:20 -0700 Subject: [PATCH 01/17] Make execHello protected This lets a subclass override it --- tests/RedisTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/RedisTest.php b/tests/RedisTest.php index cb69686671..d551f30375 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'); From 52e2b8a788863c105843119f1ad3be5adf9bfff2 Mon Sep 17 00:00:00 2001 From: michael-grunder Date: Mon, 17 Mar 2025 12:13:42 -0700 Subject: [PATCH 02/17] Prepare for 6.2.0 release --- CHANGELOG.md | 164 ++++++++++++++++++++++++++++++- package.xml | 265 ++++++++++++++++++++++++++------------------------- php_redis.h | 2 +- 3 files changed, 299 insertions(+), 132 deletions(-) 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/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 */ From 3828c9293b8fcd473e3c0b6d72c8db740b32bed8 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Wed, 26 Mar 2025 23:25:22 +0100 Subject: [PATCH 03/17] cleanup session temp file (#2641) * cleanup session temp file * Fix Deprecated: Automatic conversion of false to array --- tests/RedisTest.php | 1 + tests/SessionHelpers.php | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/RedisTest.php b/tests/RedisTest.php index d551f30375..d7f8b48059 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -6185,6 +6185,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"; 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'); } From 4f6a3ed1e71c70f80b631a9f53749e6a9fdb457a Mon Sep 17 00:00:00 2001 From: Jakub Onderka Date: Thu, 27 Mar 2025 02:05:33 +0100 Subject: [PATCH 04/17] New option 'database' for Redis class constructor (#2597) * New option 'database' for Redis class constructor Selecting database is very common action after connecting to Redis. This simplifies lazy connecting to Redis, when requested database will be selected after first command. * More specific exception message when invalid auth or database number is provided Before it was just 'Redis server went away' * Rename reselect_db method to redis_select_db and slightly optimise it --- README.md | 6 +++-- library.c | 55 ++++++++++++++++++++++++++++----------------- redis.c | 36 +++++++++++++++++++++-------- tests/RedisTest.php | 21 +++++++++++++++++ 4 files changed, 86 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 9f3389d893..68e70a2dee 100644 --- a/README.md +++ b/README.md @@ -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/library.c b/library.c index fe47f3ff4f..d51a0bd34d 100644 --- a/library.c +++ b/library.c @@ -139,31 +139,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; } @@ -252,9 +261,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 +391,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; } @@ -2857,6 +2864,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 +3242,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; diff --git a/redis.c b/redis.c index 3075437d1a..a1866476cd 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; } diff --git a/tests/RedisTest.php b/tests/RedisTest.php index d7f8b48059..5438f9545a 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -7836,6 +7836,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) From 0445e683e7552d60dbc82f21e0ee845911844651 Mon Sep 17 00:00:00 2001 From: Michael Grunder Date: Mon, 31 Mar 2025 12:42:29 -0700 Subject: [PATCH 05/17] Refactor `getWithMeta` logic (#2643) * Refactor `getWithMeta` * Consolidate `getWithMeta()` test. * Review comments --- cluster_library.c | 61 +++++++++++++++++++++++------------ cluster_library.h | 2 ++ common.h | 1 - library.c | 65 ++++++++++++++++++++++++++------------ library.h | 1 + redis.c | 17 ++-------- redis_cluster.c | 13 ++------ tests/RedisClusterTest.php | 47 --------------------------- tests/RedisTest.php | 30 ++++++++++-------- 9 files changed, 107 insertions(+), 130 deletions(-) diff --git a/cluster_library.c b/cluster_library.c index 45a60e2384..97e7ddf559 100644 --- a/cluster_library.c +++ b/cluster_library.c @@ -1672,37 +1672,56 @@ 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; + } + + if (!redis_unpack(c->flags, resp, c->reply_len, zdst)) { + ZVAL_STRINGL_FAST(zdst, resp, c->reply_len); + } + + 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); } } 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 d51a0bd34d..d6f357c113 100644 --- a/library.c +++ b/library.c @@ -250,7 +250,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; @@ -2740,35 +2739,59 @@ 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_unpack(redis_sock, resp, len, zdst)) { + ZVAL_STRINGL_FAST(zdst, resp, len); } - if (redis_sock->flags & PHPREDIS_WITH_METADATA) { - redis_with_metadata(&z_ret, &z_unpacked, response_len); - zv = &z_ret; + 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); + + if (IS_ATOMIC(redis_sock)) { + RETVAL_ZVAL(&zret, 0, 1); } else { - zv = &z_unpacked; + add_next_index_zval(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; 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/redis.c b/redis.c index a1866476cd..3f13a59888 100644 --- a/redis.c +++ b/redis.c @@ -778,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); } /* }}} */ @@ -796,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); } /* }}} */ diff --git a/redis_cluster.c b/redis_cluster.c index a412130df9..1cbd825925 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); } /* }}} */ diff --git a/tests/RedisClusterTest.php b/tests/RedisClusterTest.php index 0b1636f830..1fe68942bd 100644 --- a/tests/RedisClusterTest.php +++ b/tests/RedisClusterTest.php @@ -250,53 +250,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 5438f9545a..e7854da442 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -5869,22 +5869,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') From 60ca48f3ce80acd697863408e3633b298fa224c5 Mon Sep 17 00:00:00 2001 From: Michael Grunder Date: Tue, 1 Apr 2025 11:33:44 -0700 Subject: [PATCH 06/17] Redis Cluster does not have `SELECT`. (#2644) --- tests/RedisClusterTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/RedisClusterTest.php b/tests/RedisClusterTest.php index 1fe68942bd..04d4286298 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(); } From 0a85bd824a1506d54abe3c48a3ad12c34429b00d Mon Sep 17 00:00:00 2001 From: Jakub Onderka Date: Wed, 4 Dec 2024 09:36:04 +0100 Subject: [PATCH 07/17] Simplify redis_unpack method calling This method always unpack given string to zval, so it is not necessary to check output value --- cluster_library.c | 38 ++++++++++++-------------------------- library.c | 37 +++++++++++++++++++------------------ redis_commands.c | 4 +--- 3 files changed, 32 insertions(+), 47 deletions(-) diff --git a/cluster_library.c b/cluster_library.c index 97e7ddf559..bdf89526cf 100644 --- a/cluster_library.c +++ b/cluster_library.c @@ -1684,9 +1684,7 @@ static int cluster_bulk_resp_to_zval(redisCluster *c, zval *zdst) { return FAILURE; } - if (!redis_unpack(c->flags, resp, c->reply_len, zdst)) { - ZVAL_STRINGL_FAST(zdst, resp, c->reply_len); - } + redis_unpack(c->flags, resp, c->reply_len, zdst); efree(resp); @@ -2853,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); @@ -2893,11 +2888,8 @@ int mbulk_resp_loop_zipstr(RedisSock *redis_sock, zval *z_result, } 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); @@ -2929,14 +2921,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 *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); /* Free our key and line */ efree(key); @@ -2963,11 +2952,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/library.c b/library.c index d6f357c113..050fe3b62e 100644 --- a/library.c +++ b/library.c @@ -107,13 +107,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) { @@ -2751,9 +2744,7 @@ static int redis_bulk_resp_to_zval(RedisSock *redis_sock, zval *zdst, int *dstle return FAILURE; } - if (!redis_unpack(redis_sock, resp, len, zdst)) { - ZVAL_STRINGL_FAST(zdst, resp, len); - } + redis_unpack(redis_sock, resp, len, zdst); efree(resp); return SUCCESS; @@ -3503,7 +3494,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; @@ -3522,11 +3513,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); } } @@ -3611,9 +3604,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); @@ -4020,6 +4011,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)) { @@ -4029,7 +4026,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 diff --git a/redis_commands.c b/redis_commands.c index 1d2c23360f..d57d4c9f8b 100644 --- a/redis_commands.c +++ b/redis_commands.c @@ -6465,8 +6465,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: */ From 5208818e8c8422f33f5299aafa51e27679561a78 Mon Sep 17 00:00:00 2001 From: michael-grunder Date: Wed, 2 Apr 2025 12:22:52 -0700 Subject: [PATCH 08/17] We can use `zval_get_tmp_string` here --- cluster_library.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster_library.c b/cluster_library.c index bdf89526cf..eca2593c9f 100644 --- a/cluster_library.c +++ b/cluster_library.c @@ -2922,9 +2922,9 @@ int mbulk_resp_loop_zipdbl(RedisSock *redis_sock, zval *z_result, } else { zval zv, *z = &zv; redis_unpack(redis_sock,key,key_len, z); - zend_string *zstr = zval_get_string(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_string_release(zstr); + zend_tmp_string_release(tmp); zval_dtor(z); /* Free our key and line */ From 614b86e457532f0cc3c6f41322740e6125949721 Mon Sep 17 00:00:00 2001 From: Jakub Onderka Date: Tue, 26 Nov 2024 21:26:43 +0100 Subject: [PATCH 09/17] New macros REDIS_RESPONSE_ERROR and REDIS_RETURN_ZVAL Deduplicate code that is used in many methods. Also optimise adding new element to array in pipeline mode and returning zval in atomic mode --- library.c | 245 +++++++++++++----------------------------------------- 1 file changed, 59 insertions(+), 186 deletions(-) diff --git a/library.c b/library.c index 050fe3b62e..a9fb523e48 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 */ @@ -1182,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; } @@ -1207,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; } @@ -1292,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; } @@ -1386,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; } @@ -1420,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; } @@ -1592,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; } @@ -1656,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; } @@ -1789,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; @@ -1810,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; } @@ -1903,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; } @@ -1987,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; } @@ -2003,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; } @@ -2015,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; } @@ -2120,11 +2089,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; } @@ -2132,11 +2097,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; } @@ -2164,21 +2125,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; } @@ -2249,19 +2202,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; } @@ -2318,21 +2263,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; } @@ -2459,20 +2396,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; } @@ -2550,20 +2479,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; } @@ -2661,11 +2583,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; } @@ -2759,11 +2677,7 @@ redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, ret = redis_bulk_resp_to_zval(redis_sock, &zret, NULL); - 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 ret; } @@ -2800,11 +2714,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)) { @@ -3393,11 +3303,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) { @@ -3409,11 +3315,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; } @@ -3426,11 +3328,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; @@ -3442,11 +3340,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; } @@ -3459,11 +3353,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; } @@ -3481,11 +3371,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; } @@ -3586,11 +3472,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; } @@ -3613,11 +3495,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; @@ -4472,12 +4350,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; From 3c64b33ffe06a8929d61dd2b71ae5ea08014a455 Mon Sep 17 00:00:00 2001 From: Rory Date: Tue, 8 Apr 2025 16:31:38 +1200 Subject: [PATCH 10/17] Fix SIGABRT in PHP 8.4 with RedisArray Same fix as 6e5360d1, with PHP switching from `ZEND_ASSUME` to `ZEND_ASSERT` in zend_hash_str_update_ptr. Fixes #2648 --- redis_array_impl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"); From bfbab8925878409d0f6614c17a597e74c30574a8 Mon Sep 17 00:00:00 2001 From: Michael Giuffrida Date: Sat, 19 Apr 2025 21:30:38 -0500 Subject: [PATCH 11/17] Broaden return type for Redis::hGetAll `Redis::hGetAll()` returns an array indexed by `string`s and/or `int`s depending on the values in the hash set. The function in the PHP stub was annotated as though the array were keyed only by strings, which is tighter than reality. --- redis.stub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redis.stub.php b/redis.stub.php index 8ace66a8c5..a2cca878ad 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 * From b7a97e5ec37ade2481f875295e45a2e1b6dd5366 Mon Sep 17 00:00:00 2001 From: AkameOuO Date: Fri, 18 Apr 2025 13:32:05 +0800 Subject: [PATCH 12/17] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 68e70a2dee..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. From b48aa0d471bf7280a1365fb5b4cb7595b5920498 Mon Sep 17 00:00:00 2001 From: michael-grunder Date: Sun, 20 Apr 2025 09:59:59 -0700 Subject: [PATCH 13/17] Fix an unused variable warning --- cluster_library.c | 3 +-- redis_arginfo.h | 2 +- redis_legacy_arginfo.h | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cluster_library.c b/cluster_library.c index eca2593c9f..38e1a6505b 100644 --- a/cluster_library.c +++ b/cluster_library.c @@ -2867,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) { @@ -2884,7 +2884,6 @@ 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; diff --git a/redis_arginfo.h b/redis_arginfo.h index 08a2308ffe..7f31a5e21a 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: 3a08bc16dd5a73e721e0df8f7843acdbbb585df5 */ 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") diff --git a/redis_legacy_arginfo.h b/redis_legacy_arginfo.h index 6bfc3a39ab..a6aae1c1c2 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: 3a08bc16dd5a73e721e0df8f7843acdbbb585df5 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0) ZEND_ARG_INFO(0, options) From 593ba012ac49065343f6bbf10adca5047414ce85 Mon Sep 17 00:00:00 2001 From: michael-grunder Date: Sun, 4 May 2025 10:20:01 -0700 Subject: [PATCH 14/17] Check for `dragonfly_version` in `HELLO` response DragonflyDB will report to be Redis but also include `dragonfly_version` in the hello response, which we can use to identify the fork. Also fix parsing of the `HELLO` response for `serverName()` and `serverVersion()`. Starting in Redis 8.0 there seem to always be modules running, which the previous function was not expecting or parsing. --- library.c | 39 ++++++++++++++++++++++++++------------- redis.c | 2 +- tests/RedisTest.php | 2 ++ 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/library.c b/library.c index a9fb523e48..ce3e2672d0 100644 --- a/library.c +++ b/library.c @@ -2018,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); @@ -2063,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; } @@ -4302,7 +4315,7 @@ redis_read_multibulk_recursive(RedisSock *redis_sock, long long elements, int st elements--; } - return 0; + return SUCCESS; } static int diff --git a/redis.c b/redis.c index 3f13a59888..629dd5c20b 100644 --- a/redis.c +++ b/redis.c @@ -2131,7 +2131,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/tests/RedisTest.php b/tests/RedisTest.php index e7854da442..1ebcc61e51 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -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 */ From 7350768cd9285b7d0c5c28742eabe52cfb1b326a Mon Sep 17 00:00:00 2001 From: michael-grunder Date: Tue, 6 May 2025 10:37:21 -0700 Subject: [PATCH 15/17] Implement several hash expiration commands Commands implemented: `H[P]EXPIRE` `H[P]TTL` `H[P]EXPIREAT` `H[P]EXPIRETIME` `HPERSIST` --- library.c | 2 +- redis.c | 45 +++++++++++++ redis.stub.php | 115 +++++++++++++++++++++++++++++++++ redis_arginfo.h | 53 ++++++++++++++- redis_cluster.c | 43 ++++++++++++ redis_cluster.stub.php | 49 ++++++++++++++ redis_cluster_arginfo.h | 56 +++++++++++++++- redis_cluster_legacy_arginfo.h | 56 +++++++++++++++- redis_commands.c | 88 ++++++++++++++++++++++++- redis_commands.h | 6 ++ redis_legacy_arginfo.h | 53 ++++++++++++++- tests/RedisTest.php | 62 ++++++++++++++++++ 12 files changed, 622 insertions(+), 6 deletions(-) diff --git a/library.c b/library.c index ce3e2672d0..c73858ef51 100644 --- a/library.c +++ b/library.c @@ -2027,7 +2027,7 @@ redis_hello_response(INTERNAL_FUNCTION_PARAMETERS, array_init(&z_ret); if (redis_read_multibulk_recursive(redis_sock, numElems, 0, &z_ret) != SUCCESS || - array_zip_values_recursive(&z_ret) != SUCCESS) + array_zip_values_recursive(&z_ret) != SUCCESS) { zval_dtor(&z_ret); goto fail; diff --git a/redis.c b/redis.c index 629dd5c20b..2ef2fc8fa9 100644 --- a/redis.c +++ b/redis.c @@ -1878,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) { diff --git a/redis.stub.php b/redis.stub.php index a2cca878ad..3f95468d7a 100644 --- a/redis.stub.php +++ b/redis.stub.php @@ -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 7f31a5e21a..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: 3a08bc16dd5a73e721e0df8f7843acdbbb585df5 */ + * 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_cluster.c b/redis_cluster.c index 1cbd825925..7b63696298 100644 --- a/redis_cluster.c +++ b/redis_cluster.c @@ -1203,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 d57d4c9f8b..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) 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 a6aae1c1c2..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: 3a08bc16dd5a73e721e0df8f7843acdbbb585df5 */ + * 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/RedisTest.php b/tests/RedisTest.php index 1ebcc61e51..7ca9e6856b 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -6288,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', $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(); From 801400036946676e48f975468f2e9c28d2c17027 Mon Sep 17 00:00:00 2001 From: michael-grunder Date: Wed, 7 May 2025 09:02:38 -0700 Subject: [PATCH 16/17] Attempt to fix flaky GitHub CI tests. We often have to rerun the test suite on GitHub actions because of a hard to reproduce "Read error on connection" exception when getting a new `RedisCluster` instance. No one has ever reported this failure outside of GitHub CI and it's not clear exactly what might be going on. This commit does two main things: 1. Allows for one failure to construct a new `RedisCluster` instance but only if we detect we're running in GitHub CI. 2. Adds much more diagnostic information if we still have a fatal error (e.g. we can't connect in two tries, or some other fatal error happens). The new info includes the whole callstack before aborting as well as an attempt to manually ping the seeds with `redis-cli`. --- .github/workflows/ci.yml | 4 +- tests/RedisClusterTest.php | 80 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 6 deletions(-) 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/tests/RedisClusterTest.php b/tests/RedisClusterTest.php index 04d4286298..a9a70e2e39 100644 --- a/tests/RedisClusterTest.php +++ b/tests/RedisClusterTest.php @@ -139,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); } } From 152fdda9b15fe5e60914f43fa34f64fd6e19d90d Mon Sep 17 00:00:00 2001 From: michael-grunder Date: Wed, 7 May 2025 15:02:02 -0700 Subject: [PATCH 17/17] Fix double -> int truncation warning --- tests/RedisTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/RedisTest.php b/tests/RedisTest.php index 7ca9e6856b..783d23a9da 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -6345,7 +6345,7 @@ public function testHashExpiration() { $this->assertEquals([1], $res); // LT - should not set if the new expiration is smaller - $res = $this->redis->{$exp_cmd}('m', $ttl / 2, ['F'], 'LT'); + $res = $this->redis->{$exp_cmd}('m', intval($ttl / 2), ['F'], 'LT'); $this->assertTrue(is_array($res) && $res[0] > 0); } }