From 89de2f58624c784de6f66f1bf1a6750cbb4420d6 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Tue, 21 Jul 2026 23:12:26 -0700 Subject: [PATCH 01/37] Implement migrate command Signed-off-by: Prateek Kumar --- CHANGELOG.md | 1 + tests/ValkeyGlideClusterTest.php | 56 +++++++++ tests/ValkeyGlideTest.php | 72 +++++++++++ valkey_glide.stub.php | 23 ++++ valkey_glide_cluster.c | 5 + valkey_glide_cluster.stub.php | 7 ++ valkey_glide_commands.c | 201 +++++++++++++++++++++++++++++++ valkey_glide_commands_common.h | 15 +++ valkey_glide_core_common.c | 1 + valkey_z_php_methods.c | 5 + 10 files changed, 386 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e3d202e..257abfe1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Changes +* Add `MIGRATE` command for standalone and cluster clients ([#275](https://github.com/valkey-io/valkey-glide-php/pull/275)) * Add `BGREWRITEAOF` command for standalone and cluster clients ([#271](https://github.com/valkey-io/valkey-glide-php/pull/271)) * Add `BGSAVE`, `BGSAVE SCHEDULE`, and `BGSAVE CANCEL` commands for standalone and cluster clients ([#236](https://github.com/valkey-io/valkey-glide-php/issues/236)) * Fix pie install from Packagist using PIE pre-packaged-source download method ([#233](https://github.com/valkey-io/valkey-glide-php/pull/233)) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index 1f9758d6..5656e654 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -644,6 +644,62 @@ public function testBgRewriteAofBatch() } } + public function testMigrate() + { + $key = '{migrate_test}_' . uniqid(); + $this->valkey_glide->set($key, 'test_value'); + + // Attempt to migrate to a non-existent host - should return false (error) + $result = $this->valkey_glide->migrate('nonexistent.invalid', 6379, $key, 0, 1000); + $this->assertFalse($result); + + // Clean up + $this->valkey_glide->del($key); + } + + public function testMigrateWithOptions() + { + $key = '{migrate_opts}_' . uniqid(); + $this->valkey_glide->set($key, 'test_value'); + + // Attempt to migrate with COPY and REPLACE options + $result = $this->valkey_glide->migrate( + 'nonexistent.invalid', 6379, $key, 0, 1000, true, true + ); + $this->assertFalse($result); + + // Clean up + $this->valkey_glide->del($key); + } + + public function testMigrateSingleKeyNokey() + { + // Single non-existent key returns NOKEY + $this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, true); + + $result = $this->valkey_glide->migrate( + 'nonexistent.invalid', 6379, 'nonexistent_key', 0, 1000 + ); + $this->assertEquals('NOKEY', $result); + + $this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, false); + } + + public function testMigrateBatch() + { + if (!$this->havePipeline()) { + $this->markTestSkipped('Pipeline not supported'); + return; + } + + $this->valkey_glide->pipeline(); + $this->valkey_glide->migrate( + 'nonexistent.invalid', 6379, 'nonexistent_key', 0, 1000 + ); + $result = $this->valkey_glide->exec(); + $this->assertFalse($result[0]); + } + public function testInfo() { $fields = [ diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 1d39cce5..b5a5acd5 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2707,6 +2707,78 @@ public function testBgRewriteAofBatch() $this->assertTrue($result[0]); } + public function testMigrate() + { + $key = 'migrate_test_' . uniqid(); + $this->valkey_glide->set($key, 'test_value'); + + // Attempt to migrate to a non-existent host - should throw/return error + $result = $this->valkey_glide->migrate('nonexistent.invalid', 6379, $key, 0, 1000); + $this->assertFalse($result); + + // Clean up + $this->valkey_glide->del($key); + } + + public function testMigrateWithOptions() + { + $key = 'migrate_opts_' . uniqid(); + $this->valkey_glide->set($key, 'test_value'); + + // Attempt to migrate with COPY and REPLACE options + $result = $this->valkey_glide->migrate( + 'nonexistent.invalid', 6379, $key, 0, 1000, true, true + ); + $this->assertFalse($result); + + // Clean up + $this->valkey_glide->del($key); + } + + public function testMigrateMultiKeyNokey() + { + // Multi-key with non-existent keys returns NOKEY immediately + $this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, true); + + $result = $this->valkey_glide->migrate( + 'nonexistent.invalid', 6379, + ['nonexistent_key_1', 'nonexistent_key_2'], + 0, 1000 + ); + $this->assertEquals('NOKEY', $result); + + $this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, false); + } + + public function testMigrateSingleKeyNokey() + { + // Single non-existent key returns NOKEY + $this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, true); + + $result = $this->valkey_glide->migrate( + 'nonexistent.invalid', 6379, 'nonexistent_key', 0, 1000 + ); + $this->assertEquals('NOKEY', $result); + + $this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, false); + } + + public function testMigrateBatch() + { + if (!$this->havePipeline()) { + $this->markTestSkipped('Pipeline not supported'); + return; + } + + $this->valkey_glide->pipeline(); + $this->valkey_glide->migrate( + 'nonexistent.invalid', 6379, 'nonexistent_key', 0, 1000 + ); + $result = $this->valkey_glide->exec(); + // Non-existent key in pipeline should return NOKEY (processed as false without literal) + $this->assertFalse($result[0]); + } + /** * Valid BGSAVE response strings (used when OPT_REPLY_LITERAL is enabled). */ diff --git a/valkey_glide.stub.php b/valkey_glide.stub.php index ca262e42..f0d0dda3 100644 --- a/valkey_glide.stub.php +++ b/valkey_glide.stub.php @@ -2211,6 +2211,29 @@ public function mget(array $keys): ValkeyGlide|array|false; */ public function move(string $key, int $index): ValkeyGlide|bool; + /** + * Atomically transfer a key from the connected instance to a destination instance. + * + * On success, the key is deleted from the source instance (unless COPY is set). + * The command is atomic and blocks both instances during the transfer. + * + * @param string $host The destination host. + * @param int $port The destination port. + * @param string|array $key The key to migrate, or an array of keys for multi-key migration. + * @param int $dstdb The destination database index. + * @param int $timeout The maximum idle time in milliseconds for the transfer. + * @param bool $copy If true, do not remove the key from the source instance. + * @param bool $replace If true, replace existing key on the destination instance. + * @param mixed $credentials Optional authentication: a string password, or + * an array [username, password] for AUTH2. + * + * @return ValkeyGlide|bool|string Returns true on success, false on failure. + * With OPT_REPLY_LITERAL enabled, returns "OK" or "NOKEY". + * + * @see https://valkey.io/commands/migrate + */ + public function migrate(string $host, int $port, string|array $key, int $dstdb, int $timeout, bool $copy = false, bool $replace = false, mixed $credentials = null): ValkeyGlide|bool|string; + /** * Set one or more string keys. * diff --git a/valkey_glide_cluster.c b/valkey_glide_cluster.c index 58db0f7a..408d81c6 100644 --- a/valkey_glide_cluster.c +++ b/valkey_glide_cluster.c @@ -985,6 +985,11 @@ BGSAVE_METHOD_IMPL(ValkeyGlideCluster) BGREWRITEAOF_METHOD_IMPL(ValkeyGlideCluster) /* }}} */ +/* {{{ proto ValkeyGlideCluster::migrate(string host, int port, string key, int dstdb, int timeout + * [, bool copy, bool replace, mixed credentials]) */ +MIGRATE_METHOD_IMPL(ValkeyGlideCluster) +/* }}} */ + /* {{{ proto ValkeyGlideCluster::dbsize(string key) * proto ValkeyGlideCluster::dbsize(array host_port) */ DBSIZE_METHOD_IMPL(ValkeyGlideCluster) diff --git a/valkey_glide_cluster.stub.php b/valkey_glide_cluster.stub.php index bc46a603..85958fe4 100644 --- a/valkey_glide_cluster.stub.php +++ b/valkey_glide_cluster.stub.php @@ -486,6 +486,13 @@ public function select(int $db): ValkeyGlideCluster|bool; */ public function move(string $key, int $index): ValkeyGlideCluster|bool; + /** + * @see ValkeyGlide::migrate + * + * In cluster mode, only single-key migration is supported. + */ + public function migrate(string $host, int $port, string $key, int $dstdb, int $timeout, bool $copy = false, bool $replace = false, mixed $credentials = null): ValkeyGlideCluster|bool|string; + /** * @see https://valkey.io/commands/copy */ diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index a0115b2c..7e4c90d2 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -363,6 +363,207 @@ int execute_bgrewriteaof_command(zval* object, int argc, zval* return_value, zen return execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object); } +/* Execute a MIGRATE command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +int execute_migrate_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { + valkey_glide_object* valkey_glide; + char* host = NULL; + size_t host_len = 0; + long port; + zval* z_key = NULL; + long dstdb; + long timeout; + zend_bool copy = 0; + zend_bool replace = 0; + zval* z_credentials = NULL; + + /* Get ValkeyGlide object */ + valkey_glide = VALKEY_GLIDE_PHP_ZVAL_GET_OBJECT(valkey_glide_object, object); + if (!valkey_glide || !valkey_glide->glide_client) { + return 0; + } + + /* Parse parameters: host, port, key (string|array), dstdb, timeout, copy, replace, credentials + */ + if (zend_parse_method_parameters(argc, + object, + "Oslzll|bbz!", + &object, + ce, + &host, + &host_len, + &port, + &z_key, + &dstdb, + &timeout, + ©, + &replace, + &z_credentials) == FAILURE) { + return 0; + } + + /* Setup core command arguments */ + core_command_args_t core_args = {0}; + core_args.glide_client = valkey_glide->glide_client; + core_args.cmd_type = Migrate; + core_args.is_cluster = (ce == get_valkey_glide_cluster_ce()); + + int arg_idx = 0; + + /* arg[0]: host */ + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = host; + core_args.args[arg_idx].data.string_arg.len = host_len; + arg_idx++; + + /* arg[1]: port */ + core_args.args[arg_idx].type = CORE_ARG_TYPE_LONG; + core_args.args[arg_idx].data.long_arg.value = port; + arg_idx++; + + /* arg[2]: key or "" for multi-key */ + if (Z_TYPE_P(z_key) == IS_STRING) { + /* Single key */ + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = Z_STRVAL_P(z_key); + core_args.args[arg_idx].data.string_arg.len = Z_STRLEN_P(z_key); + } else if (Z_TYPE_P(z_key) == IS_ARRAY) { + /* Multi-key: pass empty string, keys will be appended via KEYS keyword */ + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = ""; + core_args.args[arg_idx].data.string_arg.len = 0; + } else { + return 0; + } + arg_idx++; + + /* arg[3]: destination db */ + core_args.args[arg_idx].type = CORE_ARG_TYPE_LONG; + core_args.args[arg_idx].data.long_arg.value = dstdb; + arg_idx++; + + /* arg[4]: timeout */ + core_args.args[arg_idx].type = CORE_ARG_TYPE_LONG; + core_args.args[arg_idx].data.long_arg.value = timeout; + arg_idx++; + + /* arg[5]: COPY (optional) */ + if (copy) { + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = "COPY"; + core_args.args[arg_idx].data.string_arg.len = 4; + arg_idx++; + } + + /* arg[6]: REPLACE (optional) */ + if (replace) { + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = "REPLACE"; + core_args.args[arg_idx].data.string_arg.len = 7; + arg_idx++; + } + + /* Handle credentials: AUTH password or AUTH2 username password */ + if (z_credentials && Z_TYPE_P(z_credentials) != IS_NULL) { + if (Z_TYPE_P(z_credentials) == IS_STRING) { + /* Simple password: AUTH password */ + if (arg_idx < 7) { + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = "AUTH"; + core_args.args[arg_idx].data.string_arg.len = 4; + arg_idx++; + } + if (arg_idx < 8) { + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = Z_STRVAL_P(z_credentials); + core_args.args[arg_idx].data.string_arg.len = Z_STRLEN_P(z_credentials); + arg_idx++; + } + } else if (Z_TYPE_P(z_credentials) == IS_ARRAY) { + /* Array: [username, password] for AUTH2 */ + HashTable* ht = Z_ARRVAL_P(z_credentials); + zval* z_val = NULL; + int idx = 0; + char* username = NULL; + size_t username_len = 0; + char* password = NULL; + size_t password_len = 0; + + ZEND_HASH_FOREACH_VAL(ht, z_val) { + if (Z_TYPE_P(z_val) == IS_STRING) { + if (idx == 0) { + username = Z_STRVAL_P(z_val); + username_len = Z_STRLEN_P(z_val); + } else if (idx == 1) { + password = Z_STRVAL_P(z_val); + password_len = Z_STRLEN_P(z_val); + } + } + idx++; + } + ZEND_HASH_FOREACH_END(); + + if (password && arg_idx + 2 < 8) { + if (username) { + /* AUTH2 username password */ + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = "AUTH2"; + core_args.args[arg_idx].data.string_arg.len = 5; + arg_idx++; + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = username; + core_args.args[arg_idx].data.string_arg.len = username_len; + arg_idx++; + if (arg_idx < 8) { + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = password; + core_args.args[arg_idx].data.string_arg.len = password_len; + arg_idx++; + } + } else { + /* AUTH password (array with single element) */ + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = "AUTH"; + core_args.args[arg_idx].data.string_arg.len = 4; + arg_idx++; + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = password; + core_args.args[arg_idx].data.string_arg.len = password_len; + arg_idx++; + } + } + } + } + + /* For multi-key: append KEYS keyword and keys as an array arg */ + if (Z_TYPE_P(z_key) == IS_ARRAY && arg_idx < 8) { + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = "KEYS"; + core_args.args[arg_idx].data.string_arg.len = 4; + arg_idx++; + + /* Add keys as array */ + if (arg_idx < 8) { + core_args.args[arg_idx].type = CORE_ARG_TYPE_ARRAY; + core_args.args[arg_idx].data.array_arg.array = z_key; + core_args.args[arg_idx].data.array_arg.count = + zend_hash_num_elements(Z_ARRVAL_P(z_key)); + arg_idx++; + } + } + + core_args.arg_count = arg_idx; + + /* Select processor based on OPT_REPLY_LITERAL: + * - With OPT_REPLY_LITERAL: return raw string ("OK" or "NOKEY") + * - Without OPT_REPLY_LITERAL: return bool true on success */ + z_result_processor_t processor = valkey_glide->opt_reply_literal + ? process_core_status_string_result + : process_core_status_bool_result; + + /* Execute using unified core framework */ + return execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object); +} + /* Execute a TIME command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ int execute_time_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; diff --git a/valkey_glide_commands_common.h b/valkey_glide_commands_common.h index b18af154..8dbd5b81 100644 --- a/valkey_glide_commands_common.h +++ b/valkey_glide_commands_common.h @@ -207,6 +207,7 @@ int execute_flushdb_command(zval* object, int argc, zval* return_value, zend_cla int execute_flushall_command(zval* object, int argc, zval* return_value, zend_class_entry* ce); int execute_bgsave_command(zval* object, int argc, zval* return_value, zend_class_entry* ce); int execute_bgrewriteaof_command(zval* object, int argc, zval* return_value, zend_class_entry* ce); +int execute_migrate_command(zval* object, int argc, zval* return_value, zend_class_entry* ce); int execute_time_command(zval* object, int argc, zval* return_value, zend_class_entry* ce); int execute_scan_command(zval* object, int argc, zval* return_value, zend_class_entry* ce); int execute_cluster_scan_command(const void* glide_client, @@ -721,6 +722,20 @@ int execute_unlink_command(zval* object, int argc, zval* return_value, zend_clas RETURN_FALSE; \ } +#define MIGRATE_METHOD_IMPL(class_name) \ + PHP_METHOD(class_name, migrate) { \ + if (execute_migrate_command(getThis(), \ + ZEND_NUM_ARGS(), \ + return_value, \ + strcmp(#class_name, "ValkeyGlideCluster") == 0 \ + ? get_valkey_glide_cluster_ce() \ + : get_valkey_glide_ce())) { \ + return; \ + } \ + zval_dtor(return_value); \ + RETURN_FALSE; \ + } + #define TIME_METHOD_IMPL(class_name) \ PHP_METHOD(class_name, time) { \ if (execute_time_command(getThis(), \ diff --git a/valkey_glide_core_common.c b/valkey_glide_core_common.c index da2414e3..baa2ebf2 100644 --- a/valkey_glide_core_common.c +++ b/valkey_glide_core_common.c @@ -316,6 +316,7 @@ int prepare_core_args(core_command_args_t* args, case FlushDB: case FlushAll: case BgSave: + case Migrate: case Select: case SwapDb: return prepare_message_args( diff --git a/valkey_z_php_methods.c b/valkey_z_php_methods.c index b3371574..8335314b 100644 --- a/valkey_z_php_methods.c +++ b/valkey_z_php_methods.c @@ -822,6 +822,11 @@ BGSAVE_METHOD_IMPL(ValkeyGlide) BGREWRITEAOF_METHOD_IMPL(ValkeyGlide) /* }}} */ +/* {{{ proto bool ValkeyGlide::migrate(string host, int port, string|array key, int dstdb, int + * timeout [, bool copy, bool replace, mixed credentials]) */ +MIGRATE_METHOD_IMPL(ValkeyGlide) +/* }}} */ + /* {{{ proto array ValkeyGlide::time() */ TIME_METHOD_IMPL(ValkeyGlide) /* }}} */ From e9e8846494e807257ced50c8c072a82e0952ea77 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Tue, 21 Jul 2026 23:16:38 -0700 Subject: [PATCH 02/37] Implement migrate command Signed-off-by: Prateek Kumar --- valkey_glide_commands.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index 7e4c90d2..871939b9 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -534,21 +534,26 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla } } - /* For multi-key: append KEYS keyword and keys as an array arg */ + /* For multi-key: append KEYS keyword and individual keys */ if (Z_TYPE_P(z_key) == IS_ARRAY && arg_idx < 8) { core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; core_args.args[arg_idx].data.string_arg.value = "KEYS"; core_args.args[arg_idx].data.string_arg.len = 4; arg_idx++; - /* Add keys as array */ - if (arg_idx < 8) { - core_args.args[arg_idx].type = CORE_ARG_TYPE_ARRAY; - core_args.args[arg_idx].data.array_arg.array = z_key; - core_args.args[arg_idx].data.array_arg.count = - zend_hash_num_elements(Z_ARRVAL_P(z_key)); - arg_idx++; + /* Add individual keys from the array */ + zval* z_val; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_key), z_val) { + if (arg_idx >= 8) + break; + if (Z_TYPE_P(z_val) == IS_STRING) { + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = Z_STRVAL_P(z_val); + core_args.args[arg_idx].data.string_arg.len = Z_STRLEN_P(z_val); + arg_idx++; + } } + ZEND_HASH_FOREACH_END(); } core_args.arg_count = arg_idx; From 15ec443cfc5f67566b7ecdfe9cf201f6ab373b1c Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Tue, 21 Jul 2026 23:26:28 -0700 Subject: [PATCH 03/37] Implement migrate command Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 3 ++- tests/ValkeyGlideTest.php | 4 ++-- valkey_glide_commands.c | 10 +++++----- valkey_glide_core_common.h | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index 5656e654..35a84807 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -697,7 +697,8 @@ public function testMigrateBatch() 'nonexistent.invalid', 6379, 'nonexistent_key', 0, 1000 ); $result = $this->valkey_glide->exec(); - $this->assertFalse($result[0]); + // Non-existent key returns NOKEY status (processed as true without literal) + $this->assertTrue($result[0]); } public function testInfo() diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index b5a5acd5..9b7a3b4c 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2775,8 +2775,8 @@ public function testMigrateBatch() 'nonexistent.invalid', 6379, 'nonexistent_key', 0, 1000 ); $result = $this->valkey_glide->exec(); - // Non-existent key in pipeline should return NOKEY (processed as false without literal) - $this->assertFalse($result[0]); + // Non-existent key returns NOKEY status (processed as true without literal) + $this->assertTrue($result[0]); } /** diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index 871939b9..026c9623 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -466,13 +466,13 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla if (z_credentials && Z_TYPE_P(z_credentials) != IS_NULL) { if (Z_TYPE_P(z_credentials) == IS_STRING) { /* Simple password: AUTH password */ - if (arg_idx < 7) { + if (arg_idx < 11) { core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; core_args.args[arg_idx].data.string_arg.value = "AUTH"; core_args.args[arg_idx].data.string_arg.len = 4; arg_idx++; } - if (arg_idx < 8) { + if (arg_idx < 12) { core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; core_args.args[arg_idx].data.string_arg.value = Z_STRVAL_P(z_credentials); core_args.args[arg_idx].data.string_arg.len = Z_STRLEN_P(z_credentials); @@ -502,7 +502,7 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla } ZEND_HASH_FOREACH_END(); - if (password && arg_idx + 2 < 8) { + if (password && arg_idx + 2 < 12) { if (username) { /* AUTH2 username password */ core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; @@ -513,7 +513,7 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla core_args.args[arg_idx].data.string_arg.value = username; core_args.args[arg_idx].data.string_arg.len = username_len; arg_idx++; - if (arg_idx < 8) { + if (arg_idx < 12) { core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; core_args.args[arg_idx].data.string_arg.value = password; core_args.args[arg_idx].data.string_arg.len = password_len; @@ -535,7 +535,7 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla } /* For multi-key: append KEYS keyword and individual keys */ - if (Z_TYPE_P(z_key) == IS_ARRAY && arg_idx < 8) { + if (Z_TYPE_P(z_key) == IS_ARRAY && arg_idx < 12) { core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; core_args.args[arg_idx].data.string_arg.value = "KEYS"; core_args.args[arg_idx].data.string_arg.len = 4; diff --git a/valkey_glide_core_common.h b/valkey_glide_core_common.h index 5e6f7c33..64ea8d42 100644 --- a/valkey_glide_core_common.h +++ b/valkey_glide_core_common.h @@ -105,7 +105,7 @@ typedef struct { zval* raw_options; /* Raw PHP options array for complex parsing */ size_t key_len; core_options_t options; - core_arg_t args[8]; /* Fixed arguments array - sufficient for current usage */ + core_arg_t args[12]; /* Fixed arguments array - supports MIGRATE with all options */ enum RequestType cmd_type; int arg_count; zend_bool is_cluster; /* Flag to indicate cluster mode */ From 791c80ea7b4733e2a2ce2956bfd5fd9b3e029590 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Tue, 21 Jul 2026 23:32:54 -0700 Subject: [PATCH 04/37] Fix CI lint errors Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 20 +++++++++++++++++--- tests/ValkeyGlideTest.php | 26 +++++++++++++++++++++----- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index 35a84807..eff729a2 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -664,7 +664,13 @@ public function testMigrateWithOptions() // Attempt to migrate with COPY and REPLACE options $result = $this->valkey_glide->migrate( - 'nonexistent.invalid', 6379, $key, 0, 1000, true, true + 'nonexistent.invalid', + 6379, + $key, + 0, + 1000, + true, + true ); $this->assertFalse($result); @@ -678,7 +684,11 @@ public function testMigrateSingleKeyNokey() $this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, true); $result = $this->valkey_glide->migrate( - 'nonexistent.invalid', 6379, 'nonexistent_key', 0, 1000 + 'nonexistent.invalid', + 6379, + 'nonexistent_key', + 0, + 1000 ); $this->assertEquals('NOKEY', $result); @@ -694,7 +704,11 @@ public function testMigrateBatch() $this->valkey_glide->pipeline(); $this->valkey_glide->migrate( - 'nonexistent.invalid', 6379, 'nonexistent_key', 0, 1000 + 'nonexistent.invalid', + 6379, + 'nonexistent_key', + 0, + 1000 ); $result = $this->valkey_glide->exec(); // Non-existent key returns NOKEY status (processed as true without literal) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 9b7a3b4c..6a7af48b 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2727,7 +2727,13 @@ public function testMigrateWithOptions() // Attempt to migrate with COPY and REPLACE options $result = $this->valkey_glide->migrate( - 'nonexistent.invalid', 6379, $key, 0, 1000, true, true + 'nonexistent.invalid', + 6379, + $key, + 0, + 1000, + true, + true ); $this->assertFalse($result); @@ -2741,9 +2747,11 @@ public function testMigrateMultiKeyNokey() $this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, true); $result = $this->valkey_glide->migrate( - 'nonexistent.invalid', 6379, + 'nonexistent.invalid', + 6379, ['nonexistent_key_1', 'nonexistent_key_2'], - 0, 1000 + 0, + 1000 ); $this->assertEquals('NOKEY', $result); @@ -2756,7 +2764,11 @@ public function testMigrateSingleKeyNokey() $this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, true); $result = $this->valkey_glide->migrate( - 'nonexistent.invalid', 6379, 'nonexistent_key', 0, 1000 + 'nonexistent.invalid', + 6379, + 'nonexistent_key', + 0, + 1000 ); $this->assertEquals('NOKEY', $result); @@ -2772,7 +2784,11 @@ public function testMigrateBatch() $this->valkey_glide->pipeline(); $this->valkey_glide->migrate( - 'nonexistent.invalid', 6379, 'nonexistent_key', 0, 1000 + 'nonexistent.invalid', + 6379, + 'nonexistent_key', + 0, + 1000 ); $result = $this->valkey_glide->exec(); // Non-existent key returns NOKEY status (processed as true without literal) From 3795285a928f838f458bae3fbb1a67f6d96ec8b7 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Tue, 21 Jul 2026 23:36:35 -0700 Subject: [PATCH 05/37] Fix tests Signed-off-by: Prateek Kumar --- valkey_glide_commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index 026c9623..124cd658 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -544,7 +544,7 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla /* Add individual keys from the array */ zval* z_val; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_key), z_val) { - if (arg_idx >= 8) + if (arg_idx >= 12) break; if (Z_TYPE_P(z_val) == IS_STRING) { core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; From 7e2587168d156d05e00dab978b1928f9e55972fa Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Wed, 22 Jul 2026 00:23:11 -0700 Subject: [PATCH 06/37] Fix tests Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 15 +++++++++++++++ tests/ValkeyGlideTest.php | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index eff729a2..476bb17b 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -715,6 +715,21 @@ public function testMigrateBatch() $this->assertTrue($result[0]); } + public function testMigrateMultiKeyNokey() + { + $this->markTestSkipped('Multi-key migration not supported in cluster mode'); + } + + public function testMigrateBatchMultiKey() + { + $this->markTestSkipped('Multi-key migration not supported in cluster mode'); + } + + public function testMigrateEmptyKeys() + { + $this->markTestSkipped('Multi-key migration not supported in cluster mode'); + } + public function testInfo() { $fields = [ diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 6a7af48b..e991db9a 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2795,6 +2795,39 @@ public function testMigrateBatch() $this->assertTrue($result[0]); } + public function testMigrateBatchMultiKey() + { + if (!$this->havePipeline()) { + $this->markTestSkipped('Pipeline not supported'); + return; + } + + $this->valkey_glide->pipeline(); + $this->valkey_glide->migrate( + 'nonexistent.invalid', + 6379, + ['nonexistent_key_1', 'nonexistent_key_2'], + 0, + 1000 + ); + $result = $this->valkey_glide->exec(); + // Non-existent keys return NOKEY status (processed as true without literal) + $this->assertTrue($result[0]); + } + + public function testMigrateEmptyKeys() + { + // Empty keys array should fail + $result = $this->valkey_glide->migrate( + 'nonexistent.invalid', + 6379, + [], + 0, + 1000 + ); + $this->assertFalse($result); + } + /** * Valid BGSAVE response strings (used when OPT_REPLY_LITERAL is enabled). */ From 359bba4696ab1ade0a53a872f5d07346b0a5a261 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Wed, 22 Jul 2026 00:38:49 -0700 Subject: [PATCH 07/37] Fix tests Signed-off-by: Prateek Kumar --- valkey_glide_commands.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index 124cd658..13d85231 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -427,7 +427,11 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla core_args.args[arg_idx].data.string_arg.value = Z_STRVAL_P(z_key); core_args.args[arg_idx].data.string_arg.len = Z_STRLEN_P(z_key); } else if (Z_TYPE_P(z_key) == IS_ARRAY) { - /* Multi-key: pass empty string, keys will be appended via KEYS keyword */ + /* Multi-key: validate non-empty */ + if (zend_hash_num_elements(Z_ARRVAL_P(z_key)) == 0) { + return 0; + } + /* Pass empty string as key placeholder, keys will be appended via KEYS keyword */ core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; core_args.args[arg_idx].data.string_arg.value = ""; core_args.args[arg_idx].data.string_arg.len = 0; From 1da312c65938f142064cebc661c16fa6a1817108 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Mon, 27 Jul 2026 09:28:56 -0700 Subject: [PATCH 08/37] Refactor based on previous PR reviews Signed-off-by: Prateek Kumar --- valkey_glide.stub.php | 3 +- valkey_glide_cluster.stub.php | 2 -- valkey_glide_commands.c | 7 ---- valkey_glide_core_common.c | 60 +++++++++++++++++------------------ 4 files changed, 31 insertions(+), 41 deletions(-) diff --git a/valkey_glide.stub.php b/valkey_glide.stub.php index f0d0dda3..1ef28645 100644 --- a/valkey_glide.stub.php +++ b/valkey_glide.stub.php @@ -2228,7 +2228,8 @@ public function move(string $key, int $index): ValkeyGlide|bool; * an array [username, password] for AUTH2. * * @return ValkeyGlide|bool|string Returns true on success, false on failure. - * With OPT_REPLY_LITERAL enabled, returns "OK" or "NOKEY". + * With OPT_REPLY_LITERAL enabled, returns "OK" or "NOKEY" on success, + * false on failure. * * @see https://valkey.io/commands/migrate */ diff --git a/valkey_glide_cluster.stub.php b/valkey_glide_cluster.stub.php index 85958fe4..649cb0d8 100644 --- a/valkey_glide_cluster.stub.php +++ b/valkey_glide_cluster.stub.php @@ -488,8 +488,6 @@ public function move(string $key, int $index): ValkeyGlideCluster|bool; /** * @see ValkeyGlide::migrate - * - * In cluster mode, only single-key migration is supported. */ public function migrate(string $host, int $port, string $key, int $dstdb, int $timeout, bool $copy = false, bool $replace = false, mixed $credentials = null): ValkeyGlideCluster|bool|string; diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index 13d85231..3114d97f 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -202,7 +202,6 @@ int execute_flushdb_command(zval* object, int argc, zval* return_value, zend_cla core_args.arg_count = 1; } - /* Execute using unified core framework */ return execute_and_handle_batch( valkey_glide, &core_args, process_core_bool_result, return_value, object); } @@ -257,7 +256,6 @@ int execute_flushall_command(zval* object, int argc, zval* return_value, zend_cl core_args.arg_count = 1; } - /* Execute using unified core framework */ return execute_and_handle_batch( valkey_glide, &core_args, process_core_bool_result, return_value, object); } @@ -317,7 +315,6 @@ int execute_bgsave_command(zval* object, int argc, zval* return_value, zend_clas ? process_core_status_string_result : process_core_status_bool_result; - /* Execute using unified core framework */ return execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object); } @@ -359,7 +356,6 @@ int execute_bgrewriteaof_command(zval* object, int argc, zval* return_value, zen ? process_core_status_string_result : process_core_status_bool_result; - /* Execute using unified core framework */ return execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object); } @@ -569,7 +565,6 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla ? process_core_status_string_result : process_core_status_bool_result; - /* Execute using unified core framework */ return execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object); } @@ -607,7 +602,6 @@ int execute_time_command(zval* object, int argc, zval* return_value, zend_class_ } } - /* Execute using unified core framework */ return execute_and_handle_batch( valkey_glide, &core_args, process_core_array_result, return_value, object); } @@ -673,7 +667,6 @@ int execute_unwatch_command(zval* object, int argc, zval* return_value, zend_cla return 0; } - /* Execute using core framework */ core_command_args_t args = {0}; args.glide_client = valkey_glide->glide_client; args.cmd_type = UnWatch; diff --git a/valkey_glide_core_common.c b/valkey_glide_core_common.c index baa2ebf2..dc12c417 100644 --- a/valkey_glide_core_common.c +++ b/valkey_glide_core_common.c @@ -196,26 +196,26 @@ int prepare_core_args(core_command_args_t* args, /* Determine preparation strategy based on command type */ switch (args->cmd_type) { /* Zero argument operations */ - case RandomKey: + case BgRewriteAof: + case DBSize: case Discard: case Exec: - case Time: + case RandomKey: case Role: - case DBSize: - case BgRewriteAof: + case Time: return prepare_zero_args(args, cmd_args, cmd_args_len); /* Single key operations */ - case GetDel: - case Get: - case Strlen: - case Type: - case TTL: - case PTTL: + case Dump: case ExpireTime: + case Get: + case GetDel: case PExpireTime: + case PTTL: case Persist: - case Dump: + case Strlen: + case TTL: + case Type: return prepare_key_only_args(args, cmd_args, cmd_args_len); /* Pattern-based operations */ @@ -228,24 +228,22 @@ int prepare_core_args(core_command_args_t* args, return prepare_zero_args(args, cmd_args, cmd_args_len); /* Key-value operations */ - case Set: - case SetEx: - case PSetEx: - case SetNX: - - case GetSet: - - case GetEx: case Append: - case Incr: + case Copy: case Decr: - case IncrBy: case DecrBy: - case Rename: - case RenameNX: + case GetEx: + case GetSet: + case Incr: + case IncrBy: case IncrByFloat: case Move: - case Copy: + case PSetEx: + case Rename: + case RenameNX: + case Set: + case SetEx: + case SetNX: return prepare_key_value_args( args, cmd_args, cmd_args_len, allocated_strings, allocated_count); @@ -266,10 +264,10 @@ int prepare_core_args(core_command_args_t* args, /* Multi-key operations */ case Exists: - case Touch: case MGet: - case Watch: case PfCount: + case Touch: + case Watch: /* Check if single key or multi-key operation */ if (args->key && args->key_len > 0 && args->arg_count == 0) { /* Single key: PFCOUNT key */ @@ -288,10 +286,10 @@ int prepare_core_args(core_command_args_t* args, /* Bit operations */ case BitCount: + case BitOp: case BitPos: case GetBit: case SetBit: - case BitOp: return prepare_bit_operation_args( args, cmd_args, cmd_args_len, allocated_strings, allocated_count); @@ -310,15 +308,15 @@ int prepare_core_args(core_command_args_t* args, args, cmd_args, cmd_args_len, allocated_strings, allocated_count); /* Message operations (no key, just arguments) */ - case Ping: + case BgSave: case Echo: - case Wait: - case FlushDB: case FlushAll: - case BgSave: + case FlushDB: case Migrate: + case Ping: case Select: case SwapDb: + case Wait: return prepare_message_args( args, cmd_args, cmd_args_len, allocated_strings, allocated_count); From eed267ad34d430bccebfa9dac8fb27e78299f017 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Mon, 27 Jul 2026 09:53:53 -0700 Subject: [PATCH 09/37] Refactor based on previous PR reviews Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 23 ++++++++++------------- tests/ValkeyGlideTest.php | 23 ++++++++++------------- valkey_glide_cluster.c | 2 +- valkey_glide_commands_common.h | 15 ++------------- valkey_z_php_methods.c | 2 +- 5 files changed, 24 insertions(+), 41 deletions(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index 03914981..b25fd25d 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -673,19 +673,16 @@ public function testMigrateWithOptions() public function testMigrateSingleKeyNokey() { - // Single non-existent key returns NOKEY - $this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, true); - - $result = $this->valkey_glide->migrate( - 'nonexistent.invalid', - 6379, - 'nonexistent_key', - 0, - 1000 - ); - $this->assertEquals('NOKEY', $result); - - $this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, false); + $this->withOptReplyLiteralEnabled(function () { + $result = $this->valkey_glide->migrate( + 'nonexistent.invalid', + 6379, + 'nonexistent_key', + 0, + 1000 + ); + $this->assertEquals('NOKEY', $result); + }); } public function testMigrateBatch() diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 72c139e6..6dcaaf32 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2757,19 +2757,16 @@ public function testMigrateWithOptions() public function testMigrateSingleKeyNokey() { - // Single non-existent key returns NOKEY - $this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, true); - - $result = $this->valkey_glide->migrate( - 'nonexistent.invalid', - 6379, - 'nonexistent_key', - 0, - 1000 - ); - $this->assertEquals('NOKEY', $result); - - $this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, false); + $this->withOptReplyLiteralEnabled(function () { + $result = $this->valkey_glide->migrate( + 'nonexistent.invalid', + 6379, + 'nonexistent_key', + 0, + 1000 + ); + $this->assertEquals('NOKEY', $result); + }); } public function testMigrateBatch() diff --git a/valkey_glide_cluster.c b/valkey_glide_cluster.c index 2ad988d6..0a7bdd5d 100644 --- a/valkey_glide_cluster.c +++ b/valkey_glide_cluster.c @@ -989,7 +989,7 @@ BGREWRITEAOF_METHOD_IMPL(ValkeyGlideCluster) * [, bool copy, bool replace, mixed credentials]) */ MIGRATE_METHOD_IMPL(ValkeyGlideCluster) /* }}} */ - + /* {{{ proto ValkeyGlideCluster::reset() */ RESET_METHOD_IMPL(ValkeyGlideCluster) /* }}} */ diff --git a/valkey_glide_commands_common.h b/valkey_glide_commands_common.h index 7458d6f4..f65f9ba0 100644 --- a/valkey_glide_commands_common.h +++ b/valkey_glide_commands_common.h @@ -480,19 +480,8 @@ int execute_unlink_command(zval* object, int argc, zval* return_value, zend_clas RETURN_FALSE; \ } -#define MIGRATE_METHOD_IMPL(class_name) \ - PHP_METHOD(class_name, migrate) { \ - if (execute_migrate_command(getThis(), \ - ZEND_NUM_ARGS(), \ - return_value, \ - strcmp(#class_name, "ValkeyGlideCluster") == 0 \ - ? get_valkey_glide_cluster_ce() \ - : get_valkey_glide_ce())) { \ - return; \ - } \ - zval_dtor(return_value); \ - RETURN_FALSE; \ - } +#define MIGRATE_METHOD_IMPL(class_name) \ + STANDARD_METHOD_IMPL(class_name, migrate, execute_migrate_command) #define BGSAVE_METHOD_IMPL(class_name) \ STANDARD_METHOD_IMPL(class_name, bgSave, execute_bgsave_command) diff --git a/valkey_z_php_methods.c b/valkey_z_php_methods.c index 9b9413b9..20070a59 100644 --- a/valkey_z_php_methods.c +++ b/valkey_z_php_methods.c @@ -826,7 +826,7 @@ BGREWRITEAOF_METHOD_IMPL(ValkeyGlide) * timeout [, bool copy, bool replace, mixed credentials]) */ MIGRATE_METHOD_IMPL(ValkeyGlide) /* }}} */ - + /* {{{ proto bool ValkeyGlide::reset() */ RESET_METHOD_IMPL(ValkeyGlide) /* }}} */ From bb23b75a84819842cdb428aacfb9ef78235c56b6 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Mon, 27 Jul 2026 09:57:33 -0700 Subject: [PATCH 10/37] Refactor based on previous PR reviews Signed-off-by: Prateek Kumar --- tests/ValkeyGlideTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 6dcaaf32..e940c026 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2720,7 +2720,7 @@ public function testBgRewriteAofBatch() $result = $this->valkey_glide->exec(); $this->assertTrue($result[0]); } - + public function testMigrate() { $key = '{migrate_test}_' . uniqid(); @@ -2803,7 +2803,7 @@ public function testMigrateEmptyKeys() { $this->markTestSkipped('Multi-key migration not supported in cluster mode'); } - + public function testReset() { $key = '{reset_test}_' . $this->createRandomString(); @@ -2869,7 +2869,7 @@ public function testResetBatch() $result = $this->valkey_glide->exec(); $this->assertTrue($result[0]); } - + /** * Valid BGSAVE response strings (used when OPT_REPLY_LITERAL is enabled). */ From 0cc68e846da7b8d07594690f772efa267ed68f13 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Mon, 27 Jul 2026 10:29:46 -0700 Subject: [PATCH 11/37] fix tests Signed-off-by: Prateek Kumar --- tests/ValkeyGlideTest.php | 171 ++++++++++++++++++++++++++++++-------- 1 file changed, 137 insertions(+), 34 deletions(-) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index e940c026..811870f6 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -88,6 +88,32 @@ private function createRandomString($length = 10) return bin2hex(random_bytes(ceil($length / 2))); } + /** + * Start a destination Valkey server for migrate tests. + * Returns the port number of the new server. + */ + protected function startDestinationServer(): int + { + $script = __DIR__ . '/../valkey-glide/utils/cluster_manager.py'; + $folderPath = sys_get_temp_dir() . '/migrate_dest_' . getmypid(); + $output = shell_exec("python3 $script start --prefix migrate-dest -r 0 --folder-path $folderPath 2>&1"); + if (preg_match('/CLUSTER_NODES=(.+)/', $output, $matches)) { + $hostPort = trim($matches[1]); + return (int) explode(':', $hostPort)[1]; + } + return 0; + } + + /** + * Stop the destination Valkey server used for migrate tests. + */ + protected function stopDestinationServer(): void + { + $script = __DIR__ . '/../valkey-glide/utils/cluster_manager.py'; + $folderPath = sys_get_temp_dir() . '/migrate_dest_' . getmypid(); + shell_exec("python3 $script stop --prefix migrate-dest --folder-path $folderPath 2>&1"); + } + /** * Execute a callable with OPT_REPLY_LITERAL enabled, ensuring it is * always disabled afterwards even if an exception is thrown. @@ -2723,45 +2749,62 @@ public function testBgRewriteAofBatch() public function testMigrate() { - $key = '{migrate_test}_' . uniqid(); + $key = 'migrate_' . $this->createRandomString(); $this->valkey_glide->set($key, 'test_value'); - // Attempt to migrate to a non-existent host - should return false (error) - $result = $this->valkey_glide->migrate('nonexistent.invalid', 6379, $key, 0, 1000); + // Existing key + invalid host returns false (connection error) + $result = $this->valkey_glide->migrate('nonexistent.invalid', 9999, $key, 0, 1000); $this->assertFalse($result); - // Clean up $this->valkey_glide->del($key); } - public function testMigrateWithOptions() + public function testMigrateNonExistentKeyReturnsFalse() { - $key = '{migrate_opts}_' . uniqid(); - $this->valkey_glide->set($key, 'test_value'); + // Non-existent key returns true (NOKEY is treated as success without literal) + $result = $this->valkey_glide->migrate( + 'nonexistent.invalid', + 6379, + 'nonexistent_key_' . $this->createRandomString(), + 0, + 1000 + ); + $this->assertTrue($result); + } - // Attempt to migrate with COPY and REPLACE options + public function testMigrateMultiKeyNonExistentReturnsFalse() + { + // Multi non-existent keys returns true (NOKEY) $result = $this->valkey_glide->migrate( 'nonexistent.invalid', 6379, - $key, + ['nonexistent_key1_' . $this->createRandomString(), 'nonexistent_key2_' . $this->createRandomString()], 0, - 1000, - true, - true + 1000 ); - $this->assertFalse($result); + $this->assertTrue($result); + } - // Clean up - $this->valkey_glide->del($key); + public function testMigrateEmptyKeys() + { + // Empty keys array should return false (client-side validation) + $result = $this->valkey_glide->migrate( + 'nonexistent.invalid', + 6379, + [], + 0, + 1000 + ); + $this->assertFalse($result); } - public function testMigrateSingleKeyNokey() + public function testMigrateNonExistentKeyWithReplyLiteral() { $this->withOptReplyLiteralEnabled(function () { $result = $this->valkey_glide->migrate( 'nonexistent.invalid', 6379, - 'nonexistent_key', + 'nonexistent_key_' . $this->createRandomString(), 0, 1000 ); @@ -2769,6 +2812,82 @@ public function testMigrateSingleKeyNokey() }); } + public function testMigrateSingleKeySuccess() + { + $port = $this->startDestinationServer(); + + $key = 'migrate_success_' . $this->createRandomString(); + $this->valkey_glide->set($key, 'migrate_value'); + + $result = $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000); + $this->assertTrue($result); + + // Key should be removed from source + $this->assertFalse($this->valkey_glide->exists($key)); + + $this->stopDestinationServer(); + } + + public function testMigrateMultiKeySuccess() + { + $port = $this->startDestinationServer(); + + $key1 = 'migrate_multi1_' . $this->createRandomString(); + $key2 = 'migrate_multi2_' . $this->createRandomString(); + $this->valkey_glide->set($key1, 'value1'); + $this->valkey_glide->set($key2, 'value2'); + + $result = $this->valkey_glide->migrate('127.0.0.1', $port, [$key1, $key2], 0, 5000); + $this->assertTrue($result); + + // Keys should be removed from source + $this->assertFalse($this->valkey_glide->exists($key1)); + $this->assertFalse($this->valkey_glide->exists($key2)); + + $this->stopDestinationServer(); + } + + public function testMigrateWithCopyKeyRemainsAtSource() + { + $port = $this->startDestinationServer(); + + $key = 'migrate_copy_' . $this->createRandomString(); + $this->valkey_glide->set($key, 'copy_value'); + + $result = $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000, true); + $this->assertTrue($result); + + // Key should remain at source with COPY flag + $this->assertTrue($this->valkey_glide->exists($key)); + $this->assertEquals('copy_value', $this->valkey_glide->get($key)); + + $this->valkey_glide->del($key); + $this->stopDestinationServer(); + } + + public function testMigrateWithReplaceOverwritesDestination() + { + $port = $this->startDestinationServer(); + + $key = 'migrate_replace_' . $this->createRandomString(); + $this->valkey_glide->set($key, 'source_value'); + + // First migrate to create key at destination + $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000, true); + + // Set a new value at source + $this->valkey_glide->set($key, 'new_source_value'); + + // Migrate with REPLACE - should overwrite destination + $result = $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000, false, true); + $this->assertTrue($result); + + // Key should be removed from source (no COPY flag) + $this->assertFalse($this->valkey_glide->exists($key)); + + $this->stopDestinationServer(); + } + public function testMigrateBatch() { if (!$this->havePipeline()) { @@ -2780,30 +2899,14 @@ public function testMigrateBatch() $this->valkey_glide->migrate( 'nonexistent.invalid', 6379, - 'nonexistent_key', + 'nonexistent_key_' . $this->createRandomString(), 0, 1000 ); $result = $this->valkey_glide->exec(); - // Non-existent key returns NOKEY status (processed as true without literal) $this->assertTrue($result[0]); } - public function testMigrateMultiKeyNokey() - { - $this->markTestSkipped('Multi-key migration not supported in cluster mode'); - } - - public function testMigrateBatchMultiKey() - { - $this->markTestSkipped('Multi-key migration not supported in cluster mode'); - } - - public function testMigrateEmptyKeys() - { - $this->markTestSkipped('Multi-key migration not supported in cluster mode'); - } - public function testReset() { $key = '{reset_test}_' . $this->createRandomString(); From 11c50fe843257abb5d714f4a229e242b78887656 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Mon, 27 Jul 2026 10:35:55 -0700 Subject: [PATCH 12/37] fix tests Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 93 +++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 26 deletions(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index b25fd25d..3835558d 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -642,42 +642,32 @@ public function testMigrate() $key = '{migrate_test}_' . uniqid(); $this->valkey_glide->set($key, 'test_value'); - // Attempt to migrate to a non-existent host - should return false (error) - $result = $this->valkey_glide->migrate('nonexistent.invalid', 6379, $key, 0, 1000); + // Existing key + invalid host returns false (connection error) + $result = $this->valkey_glide->migrate('nonexistent.invalid', 9999, $key, 0, 1000); $this->assertFalse($result); - // Clean up $this->valkey_glide->del($key); } - public function testMigrateWithOptions() + public function testMigrateNonExistentKeyReturnsFalse() { - $key = '{migrate_opts}_' . uniqid(); - $this->valkey_glide->set($key, 'test_value'); - - // Attempt to migrate with COPY and REPLACE options $result = $this->valkey_glide->migrate( 'nonexistent.invalid', 6379, - $key, + 'nonexistent_key_' . uniqid(), 0, - 1000, - true, - true + 1000 ); - $this->assertFalse($result); - - // Clean up - $this->valkey_glide->del($key); + $this->assertTrue($result); } - public function testMigrateSingleKeyNokey() + public function testMigrateNonExistentKeyWithReplyLiteral() { $this->withOptReplyLiteralEnabled(function () { $result = $this->valkey_glide->migrate( 'nonexistent.invalid', 6379, - 'nonexistent_key', + 'nonexistent_key_' . uniqid(), 0, 1000 ); @@ -685,6 +675,63 @@ public function testMigrateSingleKeyNokey() }); } + public function testMigrateSingleKeySuccess() + { + $port = $this->startDestinationServer(); + + $key = '{migrate_success}_' . uniqid(); + $this->valkey_glide->set($key, 'migrate_value'); + + $result = $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000); + $this->assertTrue($result); + + // Key should be removed from source + $this->assertFalse($this->valkey_glide->exists($key)); + + $this->stopDestinationServer(); + } + + public function testMigrateWithCopyKeyRemainsAtSource() + { + $port = $this->startDestinationServer(); + + $key = '{migrate_copy}_' . uniqid(); + $this->valkey_glide->set($key, 'copy_value'); + + $result = $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000, true); + $this->assertTrue($result); + + // Key should remain at source with COPY flag + $this->assertTrue($this->valkey_glide->exists($key)); + $this->assertEquals('copy_value', $this->valkey_glide->get($key)); + + $this->valkey_glide->del($key); + $this->stopDestinationServer(); + } + + public function testMigrateWithReplaceOverwritesDestination() + { + $port = $this->startDestinationServer(); + + $key = '{migrate_replace}_' . uniqid(); + $this->valkey_glide->set($key, 'source_value'); + + // First migrate with COPY to create key at destination + $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000, true); + + // Set a new value at source + $this->valkey_glide->set($key, 'new_source_value'); + + // Migrate with REPLACE - should overwrite destination + $result = $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000, false, true); + $this->assertTrue($result); + + // Key should be removed from source (no COPY flag) + $this->assertFalse($this->valkey_glide->exists($key)); + + $this->stopDestinationServer(); + } + public function testMigrateBatch() { if (!$this->havePipeline()) { @@ -696,21 +743,15 @@ public function testMigrateBatch() $this->valkey_glide->migrate( 'nonexistent.invalid', 6379, - 'nonexistent_key', + 'nonexistent_key_' . uniqid(), 0, 1000 ); $result = $this->valkey_glide->exec(); - // Non-existent key returns NOKEY status (processed as true without literal) $this->assertTrue($result[0]); } - public function testMigrateMultiKeyNokey() - { - $this->markTestSkipped('Multi-key migration not supported in cluster mode'); - } - - public function testMigrateBatchMultiKey() + public function testMigrateMultiKeyNotSupported() { $this->markTestSkipped('Multi-key migration not supported in cluster mode'); } From 64447748e6aa962e259ffba98a87aaf21852f55a Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Mon, 27 Jul 2026 10:59:08 -0700 Subject: [PATCH 13/37] fix tests Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 28 ++++++----- tests/ValkeyGlideTest.php | 72 +++++++++++++---------------- tests/start_valkey_with_replicas.sh | 10 +++- 3 files changed, 55 insertions(+), 55 deletions(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index 3835558d..2db98d1d 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -677,59 +677,57 @@ public function testMigrateNonExistentKeyWithReplyLiteral() public function testMigrateSingleKeySuccess() { - $port = $this->startDestinationServer(); + $dest = $this->getMigrateDestClient(); $key = '{migrate_success}_' . uniqid(); $this->valkey_glide->set($key, 'migrate_value'); - $result = $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000); + $result = $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000); $this->assertTrue($result); - // Key should be removed from source + // Key should be removed from source and exist at destination $this->assertFalse($this->valkey_glide->exists($key)); - - $this->stopDestinationServer(); + $this->assertEquals('migrate_value', $dest->get($key)); } public function testMigrateWithCopyKeyRemainsAtSource() { - $port = $this->startDestinationServer(); + $dest = $this->getMigrateDestClient(); $key = '{migrate_copy}_' . uniqid(); $this->valkey_glide->set($key, 'copy_value'); - $result = $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000, true); + $result = $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000, true); $this->assertTrue($result); - // Key should remain at source with COPY flag + // Key should remain at source and also exist at destination $this->assertTrue($this->valkey_glide->exists($key)); $this->assertEquals('copy_value', $this->valkey_glide->get($key)); + $this->assertEquals('copy_value', $dest->get($key)); $this->valkey_glide->del($key); - $this->stopDestinationServer(); } public function testMigrateWithReplaceOverwritesDestination() { - $port = $this->startDestinationServer(); + $dest = $this->getMigrateDestClient(); $key = '{migrate_replace}_' . uniqid(); $this->valkey_glide->set($key, 'source_value'); // First migrate with COPY to create key at destination - $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000, true); + $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000, true); // Set a new value at source $this->valkey_glide->set($key, 'new_source_value'); // Migrate with REPLACE - should overwrite destination - $result = $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000, false, true); + $result = $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000, false, true); $this->assertTrue($result); - // Key should be removed from source (no COPY flag) + // Key should be removed from source, destination has new value $this->assertFalse($this->valkey_glide->exists($key)); - - $this->stopDestinationServer(); + $this->assertEquals('new_source_value', $dest->get($key)); } public function testMigrateBatch() diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 811870f6..80539a3c 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -89,29 +89,25 @@ private function createRandomString($length = 10) } /** - * Start a destination Valkey server for migrate tests. - * Returns the port number of the new server. + * Port for the destination Valkey server used in migrate tests. */ - protected function startDestinationServer(): int - { - $script = __DIR__ . '/../valkey-glide/utils/cluster_manager.py'; - $folderPath = sys_get_temp_dir() . '/migrate_dest_' . getmypid(); - $output = shell_exec("python3 $script start --prefix migrate-dest -r 0 --folder-path $folderPath 2>&1"); - if (preg_match('/CLUSTER_NODES=(.+)/', $output, $matches)) { - $hostPort = trim($matches[1]); - return (int) explode(':', $hostPort)[1]; - } - return 0; - } + protected const MIGRATE_DEST_PORT = 6382; /** - * Stop the destination Valkey server used for migrate tests. + * Shared destination client for migrate tests. */ - protected function stopDestinationServer(): void + protected ?ValkeyGlide $migrateDestClient = null; + + protected function getMigrateDestClient(): ValkeyGlide { - $script = __DIR__ . '/../valkey-glide/utils/cluster_manager.py'; - $folderPath = sys_get_temp_dir() . '/migrate_dest_' . getmypid(); - shell_exec("python3 $script stop --prefix migrate-dest --folder-path $folderPath 2>&1"); + if ($this->migrateDestClient === null) { + $this->migrateDestClient = new ValkeyGlide(); + $this->migrateDestClient->connect( + addresses: [['host' => '127.0.0.1', 'port' => self::MIGRATE_DEST_PORT]] + ); + } + $this->migrateDestClient->flushDb(); + return $this->migrateDestClient; } /** @@ -2814,78 +2810,76 @@ public function testMigrateNonExistentKeyWithReplyLiteral() public function testMigrateSingleKeySuccess() { - $port = $this->startDestinationServer(); + $dest = $this->getMigrateDestClient(); $key = 'migrate_success_' . $this->createRandomString(); $this->valkey_glide->set($key, 'migrate_value'); - $result = $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000); + $result = $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000); $this->assertTrue($result); - // Key should be removed from source + // Key should be removed from source and exist at destination $this->assertFalse($this->valkey_glide->exists($key)); - - $this->stopDestinationServer(); + $this->assertEquals('migrate_value', $dest->get($key)); } public function testMigrateMultiKeySuccess() { - $port = $this->startDestinationServer(); + $dest = $this->getMigrateDestClient(); $key1 = 'migrate_multi1_' . $this->createRandomString(); $key2 = 'migrate_multi2_' . $this->createRandomString(); $this->valkey_glide->set($key1, 'value1'); $this->valkey_glide->set($key2, 'value2'); - $result = $this->valkey_glide->migrate('127.0.0.1', $port, [$key1, $key2], 0, 5000); + $result = $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, [$key1, $key2], 0, 5000); $this->assertTrue($result); - // Keys should be removed from source + // Keys should be removed from source and exist at destination $this->assertFalse($this->valkey_glide->exists($key1)); $this->assertFalse($this->valkey_glide->exists($key2)); - - $this->stopDestinationServer(); + $this->assertEquals('value1', $dest->get($key1)); + $this->assertEquals('value2', $dest->get($key2)); } public function testMigrateWithCopyKeyRemainsAtSource() { - $port = $this->startDestinationServer(); + $dest = $this->getMigrateDestClient(); $key = 'migrate_copy_' . $this->createRandomString(); $this->valkey_glide->set($key, 'copy_value'); - $result = $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000, true); + $result = $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000, true); $this->assertTrue($result); - // Key should remain at source with COPY flag + // Key should remain at source and also exist at destination $this->assertTrue($this->valkey_glide->exists($key)); $this->assertEquals('copy_value', $this->valkey_glide->get($key)); + $this->assertEquals('copy_value', $dest->get($key)); $this->valkey_glide->del($key); - $this->stopDestinationServer(); } public function testMigrateWithReplaceOverwritesDestination() { - $port = $this->startDestinationServer(); + $dest = $this->getMigrateDestClient(); $key = 'migrate_replace_' . $this->createRandomString(); $this->valkey_glide->set($key, 'source_value'); - // First migrate to create key at destination - $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000, true); + // First migrate with COPY to create key at destination + $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000, true); // Set a new value at source $this->valkey_glide->set($key, 'new_source_value'); // Migrate with REPLACE - should overwrite destination - $result = $this->valkey_glide->migrate('127.0.0.1', $port, $key, 0, 5000, false, true); + $result = $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000, false, true); $this->assertTrue($result); - // Key should be removed from source (no COPY flag) + // Key should be removed from source, destination has new value $this->assertFalse($this->valkey_glide->exists($key)); - - $this->stopDestinationServer(); + $this->assertEquals('new_source_value', $dest->get($key)); } public function testMigrateBatch() diff --git a/tests/start_valkey_with_replicas.sh b/tests/start_valkey_with_replicas.sh index f3c23e0d..d433793f 100755 --- a/tests/start_valkey_with_replicas.sh +++ b/tests/start_valkey_with_replicas.sh @@ -9,7 +9,7 @@ cd "$SCRIPT_DIR" BASE_DIR="$(pwd)/valkey_data" # Create data directories with full path -for port in 6379 6380 6381; do +for port in 6379 6380 6381 6382; do mkdir -p "$BASE_DIR/$port" done @@ -37,6 +37,14 @@ valkey-server --port 6381 \ --logfile "$BASE_DIR/6381/valkey.log" \ --enable-debug-command yes +# Start independent standalone server for migrate tests (6382) +valkey-server --port 6382 \ + --bind 127.0.0.1 ::1 \ + --dir "$BASE_DIR/6382" \ + --daemonize yes \ + --logfile "$BASE_DIR/6382/valkey.log" \ + --enable-debug-command yes + # Handle TLS setup with graceful failure echo "Setting up TLS standalone server..." if ../valkey-glide/utils/cluster_manager.py --tls start --prefix tls-standalone -p 6400 -r 0; then From b7912fc8a3b895cd15ecfa981d297493d591cae4 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Mon, 27 Jul 2026 11:26:02 -0700 Subject: [PATCH 14/37] fix tests Signed-off-by: Prateek Kumar --- tests/start_valkey_with_replicas.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/start_valkey_with_replicas.sh b/tests/start_valkey_with_replicas.sh index d433793f..7e442532 100755 --- a/tests/start_valkey_with_replicas.sh +++ b/tests/start_valkey_with_replicas.sh @@ -39,11 +39,12 @@ valkey-server --port 6381 \ # Start independent standalone server for migrate tests (6382) valkey-server --port 6382 \ - --bind 127.0.0.1 ::1 \ + --bind 0.0.0.0 \ --dir "$BASE_DIR/6382" \ --daemonize yes \ --logfile "$BASE_DIR/6382/valkey.log" \ - --enable-debug-command yes + --enable-debug-command yes \ + --protected-mode no # Handle TLS setup with graceful failure echo "Setting up TLS standalone server..." From d2a320e5507c925a9670fb2756ed1fc0de9446ac Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Mon, 27 Jul 2026 12:29:24 -0700 Subject: [PATCH 15/37] fix tests Signed-off-by: Prateek Kumar --- tests/ValkeyGlideTest.php | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 80539a3c..9ea5049a 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2808,6 +2808,54 @@ public function testMigrateNonExistentKeyWithReplyLiteral() }); } + public function testMigrateDebugDiagnostic() + { + // Diagnostic test to debug MIGRATE IOERR in CI + $dest = $this->getMigrateDestClient(); + + // 1. Verify dest client can connect and operate + $dest->set('test_connectivity', 'works'); + $connResult = $dest->get('test_connectivity'); + echo "\n[DIAG] Dest connectivity: " . var_export($connResult, true) . "\n"; + + // 2. Try MIGRATE with rawCommand to bypass our C wrapper + $key = 'migrate_diag_' . $this->createRandomString(); + $this->valkey_glide->set($key, 'diag_value'); + + $rawResult = $this->valkey_glide->rawCommand( + $key, 'MIGRATE', '127.0.0.1', '6382', $key, '0', '5000' + ); + echo "[DIAG] MIGRATE rawCommand result: " . var_export($rawResult, true) . "\n"; + + // 3. Try with our wrapper + $key2 = 'migrate_diag2_' . $this->createRandomString(); + $this->valkey_glide->set($key2, 'diag_value2'); + $wrapperResult = $this->valkey_glide->migrate('127.0.0.1', 6382, $key2, 0, 5000); + echo "[DIAG] MIGRATE wrapper result: " . var_export($wrapperResult, true) . "\n"; + + // 4. Check if keys exist after migration + $existsKey1 = $this->valkey_glide->exists($key); + $existsKey2 = $this->valkey_glide->exists($key2); + echo "[DIAG] Key1 exists at source: " . var_export($existsKey1, true) . "\n"; + echo "[DIAG] Key2 exists at source: " . var_export($existsKey2, true) . "\n"; + + // 5. Check if keys arrived at destination + $destKey1 = $dest->get($key); + $destKey2 = $dest->get($key2); + echo "[DIAG] Key1 at dest: " . var_export($destKey1, true) . "\n"; + echo "[DIAG] Key2 at dest: " . var_export($destKey2, true) . "\n"; + + // 6. Try with localhost instead of 127.0.0.1 + $key3 = 'migrate_diag3_' . $this->createRandomString(); + $this->valkey_glide->set($key3, 'diag_value3'); + $localhostResult = $this->valkey_glide->rawCommand( + $key3, 'MIGRATE', 'localhost', '6382', $key3, '0', '5000' + ); + echo "[DIAG] MIGRATE localhost rawCommand: " . var_export($localhostResult, true) . "\n"; + + $this->assertTrue(true); // Always pass - this is diagnostic + } + public function testMigrateSingleKeySuccess() { $dest = $this->getMigrateDestClient(); From 08a6c3ced6ef8aa76459eb12e482019524e3a657 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Mon, 27 Jul 2026 13:42:24 -0700 Subject: [PATCH 16/37] fix tests Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 6 ++-- tests/ValkeyGlideTest.php | 58 +++----------------------------- 2 files changed, 8 insertions(+), 56 deletions(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index 2db98d1d..daf6c9c7 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -686,7 +686,7 @@ public function testMigrateSingleKeySuccess() $this->assertTrue($result); // Key should be removed from source and exist at destination - $this->assertFalse($this->valkey_glide->exists($key)); + $this->assertEquals(0, $this->valkey_glide->exists($key)); $this->assertEquals('migrate_value', $dest->get($key)); } @@ -701,7 +701,7 @@ public function testMigrateWithCopyKeyRemainsAtSource() $this->assertTrue($result); // Key should remain at source and also exist at destination - $this->assertTrue($this->valkey_glide->exists($key)); + $this->assertEquals(1, $this->valkey_glide->exists($key)); $this->assertEquals('copy_value', $this->valkey_glide->get($key)); $this->assertEquals('copy_value', $dest->get($key)); @@ -726,7 +726,7 @@ public function testMigrateWithReplaceOverwritesDestination() $this->assertTrue($result); // Key should be removed from source, destination has new value - $this->assertFalse($this->valkey_glide->exists($key)); + $this->assertEquals(0, $this->valkey_glide->exists($key)); $this->assertEquals('new_source_value', $dest->get($key)); } diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 9ea5049a..368b7086 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2808,54 +2808,6 @@ public function testMigrateNonExistentKeyWithReplyLiteral() }); } - public function testMigrateDebugDiagnostic() - { - // Diagnostic test to debug MIGRATE IOERR in CI - $dest = $this->getMigrateDestClient(); - - // 1. Verify dest client can connect and operate - $dest->set('test_connectivity', 'works'); - $connResult = $dest->get('test_connectivity'); - echo "\n[DIAG] Dest connectivity: " . var_export($connResult, true) . "\n"; - - // 2. Try MIGRATE with rawCommand to bypass our C wrapper - $key = 'migrate_diag_' . $this->createRandomString(); - $this->valkey_glide->set($key, 'diag_value'); - - $rawResult = $this->valkey_glide->rawCommand( - $key, 'MIGRATE', '127.0.0.1', '6382', $key, '0', '5000' - ); - echo "[DIAG] MIGRATE rawCommand result: " . var_export($rawResult, true) . "\n"; - - // 3. Try with our wrapper - $key2 = 'migrate_diag2_' . $this->createRandomString(); - $this->valkey_glide->set($key2, 'diag_value2'); - $wrapperResult = $this->valkey_glide->migrate('127.0.0.1', 6382, $key2, 0, 5000); - echo "[DIAG] MIGRATE wrapper result: " . var_export($wrapperResult, true) . "\n"; - - // 4. Check if keys exist after migration - $existsKey1 = $this->valkey_glide->exists($key); - $existsKey2 = $this->valkey_glide->exists($key2); - echo "[DIAG] Key1 exists at source: " . var_export($existsKey1, true) . "\n"; - echo "[DIAG] Key2 exists at source: " . var_export($existsKey2, true) . "\n"; - - // 5. Check if keys arrived at destination - $destKey1 = $dest->get($key); - $destKey2 = $dest->get($key2); - echo "[DIAG] Key1 at dest: " . var_export($destKey1, true) . "\n"; - echo "[DIAG] Key2 at dest: " . var_export($destKey2, true) . "\n"; - - // 6. Try with localhost instead of 127.0.0.1 - $key3 = 'migrate_diag3_' . $this->createRandomString(); - $this->valkey_glide->set($key3, 'diag_value3'); - $localhostResult = $this->valkey_glide->rawCommand( - $key3, 'MIGRATE', 'localhost', '6382', $key3, '0', '5000' - ); - echo "[DIAG] MIGRATE localhost rawCommand: " . var_export($localhostResult, true) . "\n"; - - $this->assertTrue(true); // Always pass - this is diagnostic - } - public function testMigrateSingleKeySuccess() { $dest = $this->getMigrateDestClient(); @@ -2867,7 +2819,7 @@ public function testMigrateSingleKeySuccess() $this->assertTrue($result); // Key should be removed from source and exist at destination - $this->assertFalse($this->valkey_glide->exists($key)); + $this->assertEquals(0, $this->valkey_glide->exists($key)); $this->assertEquals('migrate_value', $dest->get($key)); } @@ -2884,8 +2836,8 @@ public function testMigrateMultiKeySuccess() $this->assertTrue($result); // Keys should be removed from source and exist at destination - $this->assertFalse($this->valkey_glide->exists($key1)); - $this->assertFalse($this->valkey_glide->exists($key2)); + $this->assertEquals(0, $this->valkey_glide->exists($key1)); + $this->assertEquals(0, $this->valkey_glide->exists($key2)); $this->assertEquals('value1', $dest->get($key1)); $this->assertEquals('value2', $dest->get($key2)); } @@ -2901,7 +2853,7 @@ public function testMigrateWithCopyKeyRemainsAtSource() $this->assertTrue($result); // Key should remain at source and also exist at destination - $this->assertTrue($this->valkey_glide->exists($key)); + $this->assertEquals(1, $this->valkey_glide->exists($key)); $this->assertEquals('copy_value', $this->valkey_glide->get($key)); $this->assertEquals('copy_value', $dest->get($key)); @@ -2926,7 +2878,7 @@ public function testMigrateWithReplaceOverwritesDestination() $this->assertTrue($result); // Key should be removed from source, destination has new value - $this->assertFalse($this->valkey_glide->exists($key)); + $this->assertEquals(0, $this->valkey_glide->exists($key)); $this->assertEquals('new_source_value', $dest->get($key)); } From 462e1065b40bb9358f840e5d14af06faa0c406d0 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Mon, 27 Jul 2026 14:54:56 -0700 Subject: [PATCH 17/37] fix tests Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index daf6c9c7..840d00f9 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -754,6 +754,16 @@ public function testMigrateMultiKeyNotSupported() $this->markTestSkipped('Multi-key migration not supported in cluster mode'); } + public function testMigrateMultiKeyNonExistentReturnsFalse() + { + $this->markTestSkipped('Multi-key migration not supported in cluster mode'); + } + + public function testMigrateMultiKeySuccess() + { + $this->markTestSkipped('Multi-key migration not supported in cluster mode'); + } + public function testMigrateEmptyKeys() { $this->markTestSkipped('Multi-key migration not supported in cluster mode'); From ca1ecb8d11786295401aa0e9a89d28c16a936fd9 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Mon, 27 Jul 2026 15:48:43 -0700 Subject: [PATCH 18/37] Fix issues Signed-off-by: Prateek Kumar --- CHANGELOG.md | 2 +- tests/ValkeyGlideClusterTest.php | 9 +++- tests/ValkeyGlideTest.php | 69 +++++++++++++++++++++++++++- valkey_glide_commands.c | 78 ++++++++++++++++++++++++-------- valkey_glide_core_common.c | 56 +++++++++++++++++++++-- valkey_glide_core_common.h | 8 ++-- 6 files changed, 193 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5d14c46..2153179c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Changes -* Add `MIGRATE` command for standalone and cluster clients ([#275](https://github.com/valkey-io/valkey-glide-php/pull/275)) +* Add `MIGRATE` command for standalone and cluster clients ([#276](https://github.com/valkey-io/valkey-glide-php/pull/276)) * Add `RESET` command for standalone and cluster clients ([#273](https://github.com/valkey-io/valkey-glide-php/pull/273)) * Add `SAVE` command for standalone and cluster clients ([#272](https://github.com/valkey-io/valkey-glide-php/pull/272)) * Add `BGREWRITEAOF` command for standalone and cluster clients ([#271](https://github.com/valkey-io/valkey-glide-php/pull/271)) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index 840d00f9..cfd2601e 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -649,7 +649,7 @@ public function testMigrate() $this->valkey_glide->del($key); } - public function testMigrateNonExistentKeyReturnsFalse() + public function testMigrateNonExistentKeyReturnsTrue() { $result = $this->valkey_glide->migrate( 'nonexistent.invalid', @@ -754,7 +754,7 @@ public function testMigrateMultiKeyNotSupported() $this->markTestSkipped('Multi-key migration not supported in cluster mode'); } - public function testMigrateMultiKeyNonExistentReturnsFalse() + public function testMigrateMultiKeyNonExistentReturnsTrue() { $this->markTestSkipped('Multi-key migration not supported in cluster mode'); } @@ -764,6 +764,11 @@ public function testMigrateMultiKeySuccess() $this->markTestSkipped('Multi-key migration not supported in cluster mode'); } + public function testMigrateBatchMultiKey() + { + $this->markTestSkipped('Multi-key migration not supported in cluster mode'); + } + public function testMigrateEmptyKeys() { $this->markTestSkipped('Multi-key migration not supported in cluster mode'); diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 368b7086..7f1a952f 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2755,7 +2755,7 @@ public function testMigrate() $this->valkey_glide->del($key); } - public function testMigrateNonExistentKeyReturnsFalse() + public function testMigrateNonExistentKeyReturnsTrue() { // Non-existent key returns true (NOKEY is treated as success without literal) $result = $this->valkey_glide->migrate( @@ -2768,7 +2768,7 @@ public function testMigrateNonExistentKeyReturnsFalse() $this->assertTrue($result); } - public function testMigrateMultiKeyNonExistentReturnsFalse() + public function testMigrateMultiKeyNonExistentReturnsTrue() { // Multi non-existent keys returns true (NOKEY) $result = $this->valkey_glide->migrate( @@ -2901,6 +2901,71 @@ public function testMigrateBatch() $this->assertTrue($result[0]); } + public function testMigrateBatchMultiKey() + { + if (!$this->havePipeline()) { + $this->markTestSkipped('Pipeline not supported'); + return; + } + + $dest = $this->getMigrateDestClient(); + + $key1 = 'migrate_batch_multi1_' . $this->createRandomString(); + $key2 = 'migrate_batch_multi2_' . $this->createRandomString(); + $this->valkey_glide->set($key1, 'value1'); + $this->valkey_glide->set($key2, 'value2'); + + $this->valkey_glide->pipeline(); + $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, [$key1, $key2], 0, 5000); + $result = $this->valkey_glide->exec(); + + $this->assertTrue($result[0]); + $this->assertEquals(0, $this->valkey_glide->exists($key1)); + $this->assertEquals(0, $this->valkey_glide->exists($key2)); + $this->assertEquals('value1', $dest->get($key1)); + $this->assertEquals('value2', $dest->get($key2)); + } + + public function testMigrateWithAuthCredentials() + { + // AUTH with string password - should not error (server may not require auth, + // but verifies the argument is correctly serialized and sent) + $key = 'migrate_auth_' . $this->createRandomString(); + $this->valkey_glide->set($key, 'auth_value'); + + // Migrate with AUTH password to invalid host - verifies credentials are passed correctly + // The connection error (false) confirms the command was built and sent, not rejected client-side + $result = $this->valkey_glide->migrate('nonexistent.invalid', 9999, $key, 0, 1000, false, false, 'mypassword'); + $this->assertFalse($result); + + $this->valkey_glide->del($key); + } + + public function testMigrateWithAuth2Credentials() + { + // AUTH2 with [username, password] array - should not error + $key = 'migrate_auth2_' . $this->createRandomString(); + $this->valkey_glide->set($key, 'auth2_value'); + + // Migrate with AUTH2 credentials to invalid host - verifies credentials are passed correctly + $result = $this->valkey_glide->migrate('nonexistent.invalid', 9999, $key, 0, 1000, false, false, ['myuser', 'mypassword']); + $this->assertFalse($result); + + $this->valkey_glide->del($key); + } + + public function testMigrateWithAuth2SingleElementArray() + { + // AUTH with single-element array [password] - should use AUTH (not AUTH2) + $key = 'migrate_auth_arr_' . $this->createRandomString(); + $this->valkey_glide->set($key, 'auth_arr_value'); + + $result = $this->valkey_glide->migrate('nonexistent.invalid', 9999, $key, 0, 1000, false, false, ['mypassword']); + $this->assertFalse($result); + + $this->valkey_glide->del($key); + } + public function testReset() { $key = '{reset_test}_' . $this->createRandomString(); diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index d5017f9b..1614523d 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -465,14 +465,12 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla /* Handle credentials: AUTH password or AUTH2 username password */ if (z_credentials && Z_TYPE_P(z_credentials) != IS_NULL) { if (Z_TYPE_P(z_credentials) == IS_STRING) { - /* Simple password: AUTH password */ - if (arg_idx < 11) { + /* Simple password: AUTH password (need 2 slots) */ + if (arg_idx + 1 < 12) { core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; core_args.args[arg_idx].data.string_arg.value = "AUTH"; core_args.args[arg_idx].data.string_arg.len = 4; arg_idx++; - } - if (arg_idx < 12) { core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; core_args.args[arg_idx].data.string_arg.value = Z_STRVAL_P(z_credentials); core_args.args[arg_idx].data.string_arg.len = Z_STRLEN_P(z_credentials); @@ -534,26 +532,62 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla } } - /* For multi-key: append KEYS keyword and individual keys */ - if (Z_TYPE_P(z_key) == IS_ARRAY && arg_idx < 12) { - core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; - core_args.args[arg_idx].data.string_arg.value = "KEYS"; - core_args.args[arg_idx].data.string_arg.len = 4; - arg_idx++; + /* For multi-key: append KEYS keyword and individual keys using dynamic args */ + if (Z_TYPE_P(z_key) == IS_ARRAY) { + int num_keys = zend_hash_num_elements(Z_ARRVAL_P(z_key)); + + /* Total items to append: 1 (KEYS keyword) + num_keys */ + int total_to_append = 1 + num_keys; + int fixed_slots_remaining = 12 - arg_idx; + int items_in_fixed = + (fixed_slots_remaining > total_to_append) ? total_to_append : fixed_slots_remaining; + int items_in_dynamic = total_to_append - items_in_fixed; + + /* Allocate dynamic overflow if needed */ + core_arg_t* dyn_buf = NULL; + if (items_in_dynamic > 0) { + dyn_buf = (core_arg_t*) ecalloc(items_in_dynamic, sizeof(core_arg_t)); + } + + int dyn_idx = 0; + int item_num = 0; + + /* Append KEYS keyword */ + if (item_num < items_in_fixed) { + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = "KEYS"; + core_args.args[arg_idx].data.string_arg.len = 4; + arg_idx++; + } else { + dyn_buf[dyn_idx].type = CORE_ARG_TYPE_STRING; + dyn_buf[dyn_idx].data.string_arg.value = "KEYS"; + dyn_buf[dyn_idx].data.string_arg.len = 4; + dyn_idx++; + } + item_num++; - /* Add individual keys from the array */ + /* Append individual keys */ zval* z_val; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_key), z_val) { - if (arg_idx >= 12) - break; if (Z_TYPE_P(z_val) == IS_STRING) { - core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; - core_args.args[arg_idx].data.string_arg.value = Z_STRVAL_P(z_val); - core_args.args[arg_idx].data.string_arg.len = Z_STRLEN_P(z_val); - arg_idx++; + if (item_num < items_in_fixed) { + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = Z_STRVAL_P(z_val); + core_args.args[arg_idx].data.string_arg.len = Z_STRLEN_P(z_val); + arg_idx++; + } else { + dyn_buf[dyn_idx].type = CORE_ARG_TYPE_STRING; + dyn_buf[dyn_idx].data.string_arg.value = Z_STRVAL_P(z_val); + dyn_buf[dyn_idx].data.string_arg.len = Z_STRLEN_P(z_val); + dyn_idx++; + } + item_num++; } } ZEND_HASH_FOREACH_END(); + + core_args.dyn_args = dyn_buf; + core_args.dyn_arg_count = dyn_idx; } core_args.arg_count = arg_idx; @@ -565,7 +599,15 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla ? process_core_status_string_result : process_core_status_bool_result; - return execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object); + int result = + execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object); + + /* Free dynamic args if allocated */ + if (core_args.dyn_args) { + efree(core_args.dyn_args); + } + + return result; } /* Execute a SAVE command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ diff --git a/valkey_glide_core_common.c b/valkey_glide_core_common.c index 0e670ff1..dd01be9e 100644 --- a/valkey_glide_core_common.c +++ b/valkey_glide_core_common.c @@ -639,11 +639,11 @@ int prepare_message_args(core_command_args_t* args, unsigned long** cmd_args_len, char*** allocated_strings, int* allocated_count) { - if (args->arg_count == 0) { + if (args->arg_count == 0 && args->dyn_arg_count == 0) { return 0; } - /* Calculate total argument count - just the arguments, no key */ + /* Calculate total argument count - fixed args + dynamic overflow args */ int total_args = 0; for (int i = 0; i < args->arg_count; i++) { @@ -658,6 +658,18 @@ int prepare_message_args(core_command_args_t* args, } } + for (int i = 0; i < args->dyn_arg_count; i++) { + switch (args->dyn_args[i].type) { + case CORE_ARG_TYPE_STRING: + case CORE_ARG_TYPE_LONG: + case CORE_ARG_TYPE_DOUBLE: + total_args++; + break; + default: + break; + } + } + if (!allocate_core_arg_arrays(total_args, cmd_args, cmd_args_len)) { return 0; } @@ -668,7 +680,7 @@ int prepare_message_args(core_command_args_t* args, int arg_idx = 0; - /* Add all arguments */ + /* Add fixed arguments */ for (int i = 0; i < args->arg_count; i++) { switch (args->args[i].type) { case CORE_ARG_TYPE_STRING: @@ -706,6 +718,44 @@ int prepare_message_args(core_command_args_t* args, } } + /* Add dynamic overflow arguments (used by MIGRATE multi-key) */ + for (int i = 0; i < args->dyn_arg_count; i++) { + switch (args->dyn_args[i].type) { + case CORE_ARG_TYPE_STRING: + (*cmd_args)[arg_idx] = (uintptr_t) args->dyn_args[i].data.string_arg.value; + (*cmd_args_len)[arg_idx] = args->dyn_args[i].data.string_arg.len; + arg_idx++; + break; + + case CORE_ARG_TYPE_LONG: { + size_t len; + char* str = core_long_to_string(args->dyn_args[i].data.long_arg.value, &len); + if (str) { + (*cmd_args)[arg_idx] = (uintptr_t) str; + (*cmd_args_len)[arg_idx] = len; + add_tracked_string(*allocated_strings, allocated_count, str); + arg_idx++; + } + break; + } + + case CORE_ARG_TYPE_DOUBLE: { + size_t len; + char* str = core_double_to_string(args->dyn_args[i].data.double_arg.value, &len); + if (str) { + (*cmd_args)[arg_idx] = (uintptr_t) str; + (*cmd_args_len)[arg_idx] = len; + add_tracked_string(*allocated_strings, allocated_count, str); + arg_idx++; + } + break; + } + + default: + break; + } + } + return arg_idx; } diff --git a/valkey_glide_core_common.h b/valkey_glide_core_common.h index 64ea8d42..f658eeff 100644 --- a/valkey_glide_core_common.h +++ b/valkey_glide_core_common.h @@ -105,11 +105,13 @@ typedef struct { zval* raw_options; /* Raw PHP options array for complex parsing */ size_t key_len; core_options_t options; - core_arg_t args[12]; /* Fixed arguments array - supports MIGRATE with all options */ + core_arg_t args[12]; /* Fixed arguments array - supports most commands */ + core_arg_t* dyn_args; /* Dynamic overflow args (heap-allocated, for MIGRATE multi-key) */ enum RequestType cmd_type; int arg_count; - zend_bool is_cluster; /* Flag to indicate cluster mode */ - zend_bool has_route; /* Flag to indicate route is provided */ + int dyn_arg_count; /* Number of dynamic overflow args */ + zend_bool is_cluster; /* Flag to indicate cluster mode */ + zend_bool has_route; /* Flag to indicate route is provided */ } core_command_args_t; /* ==================================================================== From 2c1ce7d8fa7260e8ab8b0056a89c04e52ab1afdd Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Mon, 27 Jul 2026 15:50:37 -0700 Subject: [PATCH 19/37] Fix issues Signed-off-by: Prateek Kumar --- valkey_glide_commands.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index 1614523d..112e3a55 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -511,12 +511,10 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla core_args.args[arg_idx].data.string_arg.value = username; core_args.args[arg_idx].data.string_arg.len = username_len; arg_idx++; - if (arg_idx < 12) { - core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; - core_args.args[arg_idx].data.string_arg.value = password; - core_args.args[arg_idx].data.string_arg.len = password_len; - arg_idx++; - } + core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; + core_args.args[arg_idx].data.string_arg.value = password; + core_args.args[arg_idx].data.string_arg.len = password_len; + arg_idx++; } else { /* AUTH password (array with single element) */ core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; From b71b8e933721a09febb6f448e4c50ab64769af58 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Tue, 28 Jul 2026 11:54:50 -0700 Subject: [PATCH 20/37] Fix variable name and add documentation Signed-off-by: Prateek Kumar --- valkey_glide.stub.php | 4 ++-- valkey_glide_cluster.stub.php | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/valkey_glide.stub.php b/valkey_glide.stub.php index fea21554..e20899cb 100644 --- a/valkey_glide.stub.php +++ b/valkey_glide.stub.php @@ -2234,7 +2234,7 @@ public function move(string $key, int $index): ValkeyGlide|bool; * @param string $host The destination host. * @param int $port The destination port. * @param string|array $key The key to migrate, or an array of keys for multi-key migration. - * @param int $dstdb The destination database index. + * @param int $destinationDb The destination database index. * @param int $timeout The maximum idle time in milliseconds for the transfer. * @param bool $copy If true, do not remove the key from the source instance. * @param bool $replace If true, replace existing key on the destination instance. @@ -2247,7 +2247,7 @@ public function move(string $key, int $index): ValkeyGlide|bool; * * @see https://valkey.io/commands/migrate */ - public function migrate(string $host, int $port, string|array $key, int $dstdb, int $timeout, bool $copy = false, bool $replace = false, mixed $credentials = null): ValkeyGlide|bool|string; + public function migrate(string $host, int $port, string|array $key, int $destinationDb, int $timeout, bool $copy = false, bool $replace = false, mixed $credentials = null): ValkeyGlide|bool|string; /** * Set one or more string keys. diff --git a/valkey_glide_cluster.stub.php b/valkey_glide_cluster.stub.php index ae304dd7..ca49487c 100644 --- a/valkey_glide_cluster.stub.php +++ b/valkey_glide_cluster.stub.php @@ -488,8 +488,10 @@ public function move(string $key, int $index): ValkeyGlideCluster|bool; /** * @see ValkeyGlide::migrate + * + * Note: In cluster mode, only single-key migration is supported. */ - public function migrate(string $host, int $port, string $key, int $dstdb, int $timeout, bool $copy = false, bool $replace = false, mixed $credentials = null): ValkeyGlideCluster|bool|string; + public function migrate(string $host, int $port, string $key, int $destinationDb, int $timeout, bool $copy = false, bool $replace = false, mixed $credentials = null): ValkeyGlideCluster|bool|string; /** * @see https://valkey.io/commands/copy From c9aa25a30a91ee1ea6d591dcc61997e7ed9fd091 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Tue, 28 Jul 2026 12:01:39 -0700 Subject: [PATCH 21/37] Fix variable name Signed-off-by: Prateek Kumar --- valkey_glide_cluster.c | 2 +- valkey_z_php_methods.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/valkey_glide_cluster.c b/valkey_glide_cluster.c index 0a7bdd5d..5e121786 100644 --- a/valkey_glide_cluster.c +++ b/valkey_glide_cluster.c @@ -985,7 +985,7 @@ BGSAVE_METHOD_IMPL(ValkeyGlideCluster) BGREWRITEAOF_METHOD_IMPL(ValkeyGlideCluster) /* }}} */ -/* {{{ proto ValkeyGlideCluster::migrate(string host, int port, string key, int dstdb, int timeout +/* {{{ proto ValkeyGlideCluster::migrate(string host, int port, string key, int destinationDb, int timeout * [, bool copy, bool replace, mixed credentials]) */ MIGRATE_METHOD_IMPL(ValkeyGlideCluster) /* }}} */ diff --git a/valkey_z_php_methods.c b/valkey_z_php_methods.c index 20070a59..9ca9c568 100644 --- a/valkey_z_php_methods.c +++ b/valkey_z_php_methods.c @@ -822,7 +822,7 @@ BGSAVE_METHOD_IMPL(ValkeyGlide) BGREWRITEAOF_METHOD_IMPL(ValkeyGlide) /* }}} */ -/* {{{ proto bool ValkeyGlide::migrate(string host, int port, string|array key, int dstdb, int +/* {{{ proto bool ValkeyGlide::migrate(string host, int port, string|array key, int destinationDb, int * timeout [, bool copy, bool replace, mixed credentials]) */ MIGRATE_METHOD_IMPL(ValkeyGlide) /* }}} */ From fbb982bb683400dc4262adf52336a914ba3977de Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Tue, 28 Jul 2026 12:10:12 -0700 Subject: [PATCH 22/37] Fix documentation Signed-off-by: Prateek Kumar --- tests/ValkeyGlideTest.php | 1 + valkey_glide_commands.c | 32 ++++++++++++++--------------- valkey_glide_commands_2.c | 26 +++++++++++------------ valkey_glide_commands_3.c | 4 ++-- valkey_glide_core_commands.c | 40 ++++++++++++++++++------------------ 5 files changed, 52 insertions(+), 51 deletions(-) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 7f1a952f..15d53a01 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -90,6 +90,7 @@ private function createRandomString($length = 10) /** * Port for the destination Valkey server used in migrate tests. + * See also "tests/start_valkey_with_replicas.sh". */ protected const MIGRATE_DEST_PORT = 6382; diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index 112e3a55..0597945c 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -152,7 +152,7 @@ int execute_msetnx_command(zval* object, int argc, zval* return_value, zend_clas return 0; } -/* Execute a FLUSHDB command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a FLUSHDB command using the Valkey Glide client */ int execute_flushdb_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* args = NULL; @@ -206,7 +206,7 @@ int execute_flushdb_command(zval* object, int argc, zval* return_value, zend_cla valkey_glide, &core_args, process_core_bool_result, return_value, object); } -/* Execute a FLUSHALL command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a FLUSHALL command using the Valkey Glide client */ int execute_flushall_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* args = NULL; @@ -260,7 +260,7 @@ int execute_flushall_command(zval* object, int argc, zval* return_value, zend_cl valkey_glide, &core_args, process_core_bool_result, return_value, object); } -/* Execute a BGSAVE command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a BGSAVE command using the Valkey Glide client */ int execute_bgsave_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* args = NULL; @@ -318,7 +318,7 @@ int execute_bgsave_command(zval* object, int argc, zval* return_value, zend_clas return execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object); } -/* Execute a BGREWRITEAOF command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a BGREWRITEAOF command using the Valkey Glide client */ int execute_bgrewriteaof_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* args = NULL; @@ -359,14 +359,14 @@ int execute_bgrewriteaof_command(zval* object, int argc, zval* return_value, zen return execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object); } -/* Execute a MIGRATE command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a MIGRATE command using the Valkey Glide client */ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* host = NULL; size_t host_len = 0; long port; zval* z_key = NULL; - long dstdb; + long destination_db; long timeout; zend_bool copy = 0; zend_bool replace = 0; @@ -378,7 +378,7 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla return 0; } - /* Parse parameters: host, port, key (string|array), dstdb, timeout, copy, replace, credentials + /* Parse parameters: host, port, key (string|array), destinationDb, timeout, copy, replace, credentials */ if (zend_parse_method_parameters(argc, object, @@ -389,7 +389,7 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla &host_len, &port, &z_key, - &dstdb, + &destination_db, &timeout, ©, &replace, @@ -438,7 +438,7 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla /* arg[3]: destination db */ core_args.args[arg_idx].type = CORE_ARG_TYPE_LONG; - core_args.args[arg_idx].data.long_arg.value = dstdb; + core_args.args[arg_idx].data.long_arg.value = destination_db; arg_idx++; /* arg[4]: timeout */ @@ -608,7 +608,7 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla return result; } -/* Execute a SAVE command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a SAVE command using the Valkey Glide client */ int execute_save_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* args = NULL; @@ -649,7 +649,7 @@ int execute_save_command(zval* object, int argc, zval* return_value, zend_class_ return execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object); } -/* Execute a RESET command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a RESET command using the Valkey Glide client */ int execute_reset_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; @@ -681,7 +681,7 @@ int execute_reset_command(zval* object, int argc, zval* return_value, zend_class return execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object); } -/* Execute a TIME command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a TIME command using the Valkey Glide client */ int execute_time_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* args = NULL; @@ -719,7 +719,7 @@ int execute_time_command(zval* object, int argc, zval* return_value, zend_class_ valkey_glide, &core_args, process_core_array_result, return_value, object); } -/* Execute a WATCH command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a WATCH command using the Valkey Glide client */ int execute_watch_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* z_args = NULL; @@ -765,7 +765,7 @@ int execute_watch_command(zval* object, int argc, zval* return_value, zend_class return 0; } -/* Execute an UNWATCH command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute an UNWATCH command using the Valkey Glide client */ int execute_unwatch_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; @@ -1194,7 +1194,7 @@ int execute_select_command_internal(valkey_glide_object* valkey_glide, return execute_core_command(valkey_glide, &args, NULL, process_core_bool_result, return_value); } -/* Execute a SELECT command - UNIFIED IMPLEMENTATION */ +/* Execute a SELECT command */ int execute_select_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; long dbindex; @@ -1245,7 +1245,7 @@ int execute_move_command_internal(valkey_glide_object* valkey_glide, return execute_core_command(valkey_glide, &args, NULL, process_core_bool_result, return_value); } -/* Execute a MOVE command - UNIFIED IMPLEMENTATION */ +/* Execute a MOVE command */ int execute_move_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; diff --git a/valkey_glide_commands_2.c b/valkey_glide_commands_2.c index 73237b69..570cecf8 100644 --- a/valkey_glide_commands_2.c +++ b/valkey_glide_commands_2.c @@ -29,7 +29,7 @@ extern char* long_to_string(long value, size_t* len); extern char* double_to_string(double value, size_t* len); -/* Execute a RENAME command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a RENAME command using the Valkey Glide client */ int execute_rename_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char * src = NULL, *dst = NULL; @@ -78,7 +78,7 @@ int execute_rename_command(zval* object, int argc, zval* return_value, zend_clas } } -/* Execute a RENAMENX command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a RENAMENX command using the Valkey Glide client */ int execute_renamenx_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char * src = NULL, *dst = NULL; @@ -126,7 +126,7 @@ int execute_renamenx_command(zval* object, int argc, zval* return_value, zend_cl return 0; } } -/* Execute a GETDEL command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a GETDEL command using the Valkey Glide client */ int execute_getdel_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -172,7 +172,7 @@ int execute_getdel_command(zval* object, int argc, zval* return_value, zend_clas } } -/* Execute a GETEX command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a GETEX command using the Valkey Glide client */ int execute_getex_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -226,7 +226,7 @@ int execute_getex_command(zval* object, int argc, zval* return_value, zend_class } } -/* Execute an INCR command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute an INCR command using the Valkey Glide client */ int execute_incr_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -301,7 +301,7 @@ int execute_incr_command(zval* object, int argc, zval* return_value, zend_class_ return 1; } -/* Execute an INCRBY command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute an INCRBY command using the Valkey Glide client */ int execute_incrby_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -345,7 +345,7 @@ int execute_incrby_command(zval* object, int argc, zval* return_value, zend_clas return 1; } -/* Execute an INCRBYFLOAT command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute an INCRBYFLOAT command using the Valkey Glide client */ int execute_incrbyfloat_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -390,7 +390,7 @@ int execute_incrbyfloat_command(zval* object, int argc, zval* return_value, zend } } -/* Execute a DECR command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a DECR command using the Valkey Glide client */ int execute_decr_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -465,7 +465,7 @@ int execute_decr_command(zval* object, int argc, zval* return_value, zend_class_ return 0; } -/* Execute a DECRBY command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a DECRBY command using the Valkey Glide client */ int execute_decrby_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -510,7 +510,7 @@ int execute_decrby_command(zval* object, int argc, zval* return_value, zend_clas } } -/* Execute an MGET command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute an MGET command using the Valkey Glide client */ int execute_mget_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* z_array; @@ -549,7 +549,7 @@ int execute_mget_command(zval* object, int argc, zval* return_value, zend_class_ } } -/* Execute an EXISTS command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute an EXISTS command using the Valkey Glide client */ int execute_exists_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* z_args; @@ -604,7 +604,7 @@ int execute_exists_command(zval* object, int argc, zval* return_value, zend_clas } } -/* Execute a TOUCH command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a TOUCH command using the Valkey Glide client */ int execute_touch_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* z_args; @@ -660,7 +660,7 @@ int execute_touch_command(zval* object, int argc, zval* return_value, zend_class } } -/* Execute an UNLINK command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute an UNLINK command using the Valkey Glide client */ int execute_unlink_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* z_args; diff --git a/valkey_glide_commands_3.c b/valkey_glide_commands_3.c index 64ef8f0f..58ffc2a9 100644 --- a/valkey_glide_commands_3.c +++ b/valkey_glide_commands_3.c @@ -2004,7 +2004,7 @@ int execute_client_command(zval* object, int argc, zval* return_value, zend_clas return 0; } -/* Execute rawcommand command - UNIFIED IMPLEMENTATION */ +/* Execute rawcommand command */ int execute_rawcommand_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* z_args = NULL; @@ -2057,7 +2057,7 @@ int execute_rawcommand_command(zval* object, int argc, zval* return_value, zend_ return 0; } -/* Execute dbSize command - UNIFIED IMPLEMENTATION */ +/* Execute dbSize command */ int execute_dbsize_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* args = NULL; diff --git a/valkey_glide_core_commands.c b/valkey_glide_core_commands.c index 76d3c051..9c1b7b87 100644 --- a/valkey_glide_core_commands.c +++ b/valkey_glide_core_commands.c @@ -439,7 +439,7 @@ int process_ping_result(CommandResponse* response, void* output, zval* return_va /* These functions are now defined in command_response.c */ -/* Execute a BITCOUNT command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a BITCOUNT command using the Valkey Glide client */ int execute_bitcount_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -486,7 +486,7 @@ int execute_bitcount_command(zval* object, int argc, zval* return_value, zend_cl return 0; } -/* Execute a BITOP command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a BITOP command using the Valkey Glide client */ int execute_bitop_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char * op = NULL, *key = NULL; @@ -551,7 +551,7 @@ int execute_bitop_command(zval* object, int argc, zval* return_value, zend_class return 0; } -/* Execute a BITPOS command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a BITPOS command using the Valkey Glide client */ int execute_bitpos_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -605,7 +605,7 @@ int execute_bitpos_command(zval* object, int argc, zval* return_value, zend_clas return 0; } -/* Execute a SET command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a SET command using the Valkey Glide client */ int execute_set_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval * z_value, *z_expire = NULL, *z_opts = NULL; @@ -786,7 +786,7 @@ int execute_set_command_internal(valkey_glide_object* valkey_glide, return execute_core_command(valkey_glide, &args, result_data, process_set_result, return_value); } -/* Execute a SETEX command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a SETEX command using the Valkey Glide client */ int execute_setex_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char * key = NULL, *val = NULL; @@ -822,7 +822,7 @@ int execute_setex_command(zval* object, int argc, zval* return_value, zend_class } } -/* Execute a PSETEX command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a PSETEX command using the Valkey Glide client */ int execute_psetex_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char * key = NULL, *val = NULL; @@ -868,7 +868,7 @@ int execute_psetex_command(zval* object, int argc, zval* return_value, zend_clas } } -/* Execute a SETNX command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a SETNX command using the Valkey Glide client */ int execute_setnx_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char * key = NULL, *val = NULL; @@ -923,7 +923,7 @@ void close_glide_client(const void* glide_client) { close_client(glide_client); } -/* Execute an ECHO command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute an ECHO command using the Valkey Glide client */ int execute_echo_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* args = NULL; @@ -1006,7 +1006,7 @@ int execute_echo_command(zval* object, int argc, zval* return_value, zend_class_ return 0; } -/* Execute a PING command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a PING command using the Valkey Glide client */ int execute_ping_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* args = NULL; @@ -1388,7 +1388,7 @@ int execute_info_command(zval* object, int argc, zval* return_value, zend_class_ return 0; } -/* Execute a GET command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a GET command using the Valkey Glide client */ int execute_get_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -1429,7 +1429,7 @@ int execute_get_command(zval* object, int argc, zval* return_value, zend_class_e return 0; } -/* Execute a RANDOMKEY command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a RANDOMKEY command using the Valkey Glide client */ int execute_randomkey_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; zval* args = NULL; @@ -1512,7 +1512,7 @@ int execute_randomkey_command(zval* object, int argc, zval* return_value, zend_c return 0; } -/* Execute a GETBIT command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a GETBIT command using the Valkey Glide client */ int execute_getbit_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -1557,7 +1557,7 @@ int execute_getbit_command(zval* object, int argc, zval* return_value, zend_clas return 0; } -/* Execute a SETBIT command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a SETBIT command using the Valkey Glide client */ int execute_setbit_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -1657,7 +1657,7 @@ int execute_del_array(const void* glide_client, return result; } -/* Execute a DEL command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a DEL command using the Valkey Glide client */ int execute_del_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; long result_value = 0; @@ -1744,7 +1744,7 @@ int execute_unlink_array(const void* glide_client, return result; } -/* Execute a STRLEN command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a STRLEN command using the Valkey Glide client */ int execute_strlen_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -1783,7 +1783,7 @@ int execute_strlen_command(zval* object, int argc, zval* return_value, zend_clas return 0; } -/* Execute a SETRANGE command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a SETRANGE command using the Valkey Glide client */ int execute_setrange_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char * key = NULL, *val = NULL; @@ -1868,7 +1868,7 @@ static void process_sorted_set_elements(struct CommandResponse* elements_resp, } } -/* Execute a TTL command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a TTL command using the Valkey Glide client */ int execute_ttl_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -1906,7 +1906,7 @@ int execute_ttl_command(zval* object, int argc, zval* return_value, zend_class_e return 0; } -/* Execute a PTTL command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a PTTL command using the Valkey Glide client */ int execute_pttl_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char* key = NULL; @@ -1944,7 +1944,7 @@ int execute_pttl_command(zval* object, int argc, zval* return_value, zend_class_ return 0; } -/* Execute a GETSET command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a GETSET command using the Valkey Glide client */ int execute_getset_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char * key = NULL, *val = NULL; @@ -2058,7 +2058,7 @@ static int process_lcs_result(CommandResponse* response, void* output, zval* ret return ret_val; } -/* Execute an LCS command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute an LCS command using the Valkey Glide client */ int execute_lcs_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; char * key1 = NULL, *key2 = NULL; From 572783fad09d41df10b15778aff376b70a3e9e7c Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Tue, 28 Jul 2026 12:59:08 -0700 Subject: [PATCH 23/37] Fix test name Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 2 +- tests/ValkeyGlideTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index cfd2601e..a71bef7c 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -637,7 +637,7 @@ public function testBgRewriteAofBatch() } } - public function testMigrate() + public function testMigrateToInvalidHostReturnsFalse() { $key = '{migrate_test}_' . uniqid(); $this->valkey_glide->set($key, 'test_value'); diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 15d53a01..36884c84 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2744,7 +2744,7 @@ public function testBgRewriteAofBatch() $this->assertTrue($result[0]); } - public function testMigrate() + public function testMigrateToInvalidHostReturnsFalse() { $key = 'migrate_' . $this->createRandomString(); $this->valkey_glide->set($key, 'test_value'); From 991f1f10d790aff22ed516f1ced9684732e898d6 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Tue, 28 Jul 2026 14:25:41 -0700 Subject: [PATCH 24/37] Fix review comments Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 5 +++ tests/ValkeyGlideTest.php | 36 ++++++++++++++++ valkey_glide_commands.c | 66 +++++++++-------------------- valkey_glide_core_common.c | 71 ++++++-------------------------- valkey_glide_core_common.h | 21 +++++----- valkey_z_php_methods.c | 4 +- 6 files changed, 85 insertions(+), 118 deletions(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index a71bef7c..3761923c 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -764,6 +764,11 @@ public function testMigrateMultiKeySuccess() $this->markTestSkipped('Multi-key migration not supported in cluster mode'); } + public function testMigrateMultiKeyManyKeys() + { + $this->markTestSkipped('Multi-key migration not supported in cluster mode'); + } + public function testMigrateBatchMultiKey() { $this->markTestSkipped('Multi-key migration not supported in cluster mode'); diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 36884c84..e27675f4 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2843,6 +2843,42 @@ public function testMigrateMultiKeySuccess() $this->assertEquals('value2', $dest->get($key2)); } + public function testMigrateMultiKeyManyKeys() + { + $dest = $this->getMigrateDestClient(); + + // Create 10 keys to exercise dynamic allocation (total args > 12) + $keys = []; + for ($i = 0; $i < 10; $i++) { + $key = 'migrate_many_' . $i . '_' . $this->createRandomString(); + $this->valkey_glide->set($key, "value_$i"); + $keys[] = $key; + } + + // Migrate with COPY + REPLACE — args: host, port, "", db, timeout, COPY, REPLACE, KEYS, key0..key9 + $result = $this->valkey_glide->migrate( + '127.0.0.1', + self::MIGRATE_DEST_PORT, + $keys, + 0, + 5000, + true, + true + ); + $this->assertTrue($result); + + // With COPY, keys remain at source + for ($i = 0; $i < 10; $i++) { + $this->assertEquals(1, $this->valkey_glide->exists($keys[$i])); + $this->assertEquals("value_$i", $dest->get($keys[$i])); + } + + // Cleanup + foreach ($keys as $key) { + $this->valkey_glide->del($key); + } + } + public function testMigrateWithCopyKeyRemainsAtSource() { $dest = $this->getMigrateDestClient(); diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index 0597945c..c307ba11 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -378,7 +378,8 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla return 0; } - /* Parse parameters: host, port, key (string|array), destinationDb, timeout, copy, replace, credentials + /* Parse parameters: host, port, key (string|array), destinationDb, timeout, copy, replace, + * credentials */ if (zend_parse_method_parameters(argc, object, @@ -530,62 +531,35 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla } } - /* For multi-key: append KEYS keyword and individual keys using dynamic args */ + /* For multi-key: allocate a single combined array with all args */ if (Z_TYPE_P(z_key) == IS_ARRAY) { - int num_keys = zend_hash_num_elements(Z_ARRVAL_P(z_key)); - - /* Total items to append: 1 (KEYS keyword) + num_keys */ - int total_to_append = 1 + num_keys; - int fixed_slots_remaining = 12 - arg_idx; - int items_in_fixed = - (fixed_slots_remaining > total_to_append) ? total_to_append : fixed_slots_remaining; - int items_in_dynamic = total_to_append - items_in_fixed; - - /* Allocate dynamic overflow if needed */ - core_arg_t* dyn_buf = NULL; - if (items_in_dynamic > 0) { - dyn_buf = (core_arg_t*) ecalloc(items_in_dynamic, sizeof(core_arg_t)); - } + int num_keys = zend_hash_num_elements(Z_ARRVAL_P(z_key)); + int total_count = arg_idx + 1 + num_keys; /* existing args + KEYS keyword + keys */ + + core_arg_t* all = (core_arg_t*) ecalloc(total_count, sizeof(core_arg_t)); - int dyn_idx = 0; - int item_num = 0; + /* Copy existing fixed args into the combined array */ + memcpy(all, core_args.args, arg_idx * sizeof(core_arg_t)); /* Append KEYS keyword */ - if (item_num < items_in_fixed) { - core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; - core_args.args[arg_idx].data.string_arg.value = "KEYS"; - core_args.args[arg_idx].data.string_arg.len = 4; - arg_idx++; - } else { - dyn_buf[dyn_idx].type = CORE_ARG_TYPE_STRING; - dyn_buf[dyn_idx].data.string_arg.value = "KEYS"; - dyn_buf[dyn_idx].data.string_arg.len = 4; - dyn_idx++; - } - item_num++; + all[arg_idx].type = CORE_ARG_TYPE_STRING; + all[arg_idx].data.string_arg.value = "KEYS"; + all[arg_idx].data.string_arg.len = 4; + arg_idx++; /* Append individual keys */ zval* z_val; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_key), z_val) { if (Z_TYPE_P(z_val) == IS_STRING) { - if (item_num < items_in_fixed) { - core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; - core_args.args[arg_idx].data.string_arg.value = Z_STRVAL_P(z_val); - core_args.args[arg_idx].data.string_arg.len = Z_STRLEN_P(z_val); - arg_idx++; - } else { - dyn_buf[dyn_idx].type = CORE_ARG_TYPE_STRING; - dyn_buf[dyn_idx].data.string_arg.value = Z_STRVAL_P(z_val); - dyn_buf[dyn_idx].data.string_arg.len = Z_STRLEN_P(z_val); - dyn_idx++; - } - item_num++; + all[arg_idx].type = CORE_ARG_TYPE_STRING; + all[arg_idx].data.string_arg.value = Z_STRVAL_P(z_val); + all[arg_idx].data.string_arg.len = Z_STRLEN_P(z_val); + arg_idx++; } } ZEND_HASH_FOREACH_END(); - core_args.dyn_args = dyn_buf; - core_args.dyn_arg_count = dyn_idx; + core_args.all_args = all; } core_args.arg_count = arg_idx; @@ -601,8 +575,8 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object); /* Free dynamic args if allocated */ - if (core_args.dyn_args) { - efree(core_args.dyn_args); + if (core_args.all_args) { + efree(core_args.all_args); } return result; diff --git a/valkey_glide_core_common.c b/valkey_glide_core_common.c index dd01be9e..79126a32 100644 --- a/valkey_glide_core_common.c +++ b/valkey_glide_core_common.c @@ -639,27 +639,18 @@ int prepare_message_args(core_command_args_t* args, unsigned long** cmd_args_len, char*** allocated_strings, int* allocated_count) { - if (args->arg_count == 0 && args->dyn_arg_count == 0) { + if (args->arg_count == 0) { return 0; } - /* Calculate total argument count - fixed args + dynamic overflow args */ + /* Determine which arg array to iterate */ + core_arg_t* arg_array = args->all_args ? args->all_args : args->args; + + /* Calculate total argument count */ int total_args = 0; for (int i = 0; i < args->arg_count; i++) { - switch (args->args[i].type) { - case CORE_ARG_TYPE_STRING: - case CORE_ARG_TYPE_LONG: - case CORE_ARG_TYPE_DOUBLE: - total_args++; - break; - default: - break; - } - } - - for (int i = 0; i < args->dyn_arg_count; i++) { - switch (args->dyn_args[i].type) { + switch (arg_array[i].type) { case CORE_ARG_TYPE_STRING: case CORE_ARG_TYPE_LONG: case CORE_ARG_TYPE_DOUBLE: @@ -680,56 +671,18 @@ int prepare_message_args(core_command_args_t* args, int arg_idx = 0; - /* Add fixed arguments */ + /* Add all arguments from the single array */ for (int i = 0; i < args->arg_count; i++) { - switch (args->args[i].type) { - case CORE_ARG_TYPE_STRING: - (*cmd_args)[arg_idx] = (uintptr_t) args->args[i].data.string_arg.value; - (*cmd_args_len)[arg_idx] = args->args[i].data.string_arg.len; - arg_idx++; - break; - - case CORE_ARG_TYPE_LONG: { - size_t len; - char* str = core_long_to_string(args->args[i].data.long_arg.value, &len); - if (str) { - (*cmd_args)[arg_idx] = (uintptr_t) str; - (*cmd_args_len)[arg_idx] = len; - add_tracked_string(*allocated_strings, allocated_count, str); - arg_idx++; - } - break; - } - - case CORE_ARG_TYPE_DOUBLE: { - size_t len; - char* str = core_double_to_string(args->args[i].data.double_arg.value, &len); - if (str) { - (*cmd_args)[arg_idx] = (uintptr_t) str; - (*cmd_args_len)[arg_idx] = len; - add_tracked_string(*allocated_strings, allocated_count, str); - arg_idx++; - } - break; - } - - default: - break; - } - } - - /* Add dynamic overflow arguments (used by MIGRATE multi-key) */ - for (int i = 0; i < args->dyn_arg_count; i++) { - switch (args->dyn_args[i].type) { + switch (arg_array[i].type) { case CORE_ARG_TYPE_STRING: - (*cmd_args)[arg_idx] = (uintptr_t) args->dyn_args[i].data.string_arg.value; - (*cmd_args_len)[arg_idx] = args->dyn_args[i].data.string_arg.len; + (*cmd_args)[arg_idx] = (uintptr_t) arg_array[i].data.string_arg.value; + (*cmd_args_len)[arg_idx] = arg_array[i].data.string_arg.len; arg_idx++; break; case CORE_ARG_TYPE_LONG: { size_t len; - char* str = core_long_to_string(args->dyn_args[i].data.long_arg.value, &len); + char* str = core_long_to_string(arg_array[i].data.long_arg.value, &len); if (str) { (*cmd_args)[arg_idx] = (uintptr_t) str; (*cmd_args_len)[arg_idx] = len; @@ -741,7 +694,7 @@ int prepare_message_args(core_command_args_t* args, case CORE_ARG_TYPE_DOUBLE: { size_t len; - char* str = core_double_to_string(args->dyn_args[i].data.double_arg.value, &len); + char* str = core_double_to_string(arg_array[i].data.double_arg.value, &len); if (str) { (*cmd_args)[arg_idx] = (uintptr_t) str; (*cmd_args_len)[arg_idx] = len; diff --git a/valkey_glide_core_common.h b/valkey_glide_core_common.h index f658eeff..02d9a2f4 100644 --- a/valkey_glide_core_common.h +++ b/valkey_glide_core_common.h @@ -99,19 +99,18 @@ typedef struct { /* Argument allocation type */ /* Core command arguments structure - simplified without dynamic support */ typedef struct { - const void* glide_client; - const char* key; - zval* route_param; /* Route parameter for cluster commands */ - zval* raw_options; /* Raw PHP options array for complex parsing */ - size_t key_len; - core_options_t options; - core_arg_t args[12]; /* Fixed arguments array - supports most commands */ - core_arg_t* dyn_args; /* Dynamic overflow args (heap-allocated, for MIGRATE multi-key) */ + const void* glide_client; + const char* key; + zval* route_param; /* Route parameter for cluster commands */ + zval* raw_options; /* Raw PHP options array for complex parsing */ + size_t key_len; + core_options_t options; + core_arg_t args[12]; /* Fixed arguments array - supports most commands */ + core_arg_t* all_args; /* If non-NULL, use this dynamically allocated array instead of args[] */ enum RequestType cmd_type; int arg_count; - int dyn_arg_count; /* Number of dynamic overflow args */ - zend_bool is_cluster; /* Flag to indicate cluster mode */ - zend_bool has_route; /* Flag to indicate route is provided */ + zend_bool is_cluster; /* Flag to indicate cluster mode */ + zend_bool has_route; /* Flag to indicate route is provided */ } core_command_args_t; /* ==================================================================== diff --git a/valkey_z_php_methods.c b/valkey_z_php_methods.c index 9ca9c568..7cd74d2f 100644 --- a/valkey_z_php_methods.c +++ b/valkey_z_php_methods.c @@ -822,8 +822,8 @@ BGSAVE_METHOD_IMPL(ValkeyGlide) BGREWRITEAOF_METHOD_IMPL(ValkeyGlide) /* }}} */ -/* {{{ proto bool ValkeyGlide::migrate(string host, int port, string|array key, int destinationDb, int - * timeout [, bool copy, bool replace, mixed credentials]) */ +/* {{{ proto bool ValkeyGlide::migrate(string host, int port, string|array key, int destinationDb, + * int timeout [, bool copy, bool replace, mixed credentials]) */ MIGRATE_METHOD_IMPL(ValkeyGlide) /* }}} */ From 3ffa4e0d7fc32d1c3d799cbee41a42dc87d8edc6 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Tue, 28 Jul 2026 14:55:07 -0700 Subject: [PATCH 25/37] Fix review comments Signed-off-by: Prateek Kumar --- tests/stop-servers.sh | 2 +- valkey_glide_commands.c | 75 +++++++++++++++++------------------------ 2 files changed, 32 insertions(+), 45 deletions(-) diff --git a/tests/stop-servers.sh b/tests/stop-servers.sh index 6494ea4d..9ab2aaad 100755 --- a/tests/stop-servers.sh +++ b/tests/stop-servers.sh @@ -12,7 +12,7 @@ echo "Stopping all Valkey servers..." # ------------------ # Non-TLS standalone servers. -for port in 6379 6380 6381; do +for port in 6379 6380 6381 6382; do valkey-cli -p $port shutdown nosave 2>/dev/null || true done diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index c307ba11..aa063db5 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -478,53 +478,39 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla arg_idx++; } } else if (Z_TYPE_P(z_credentials) == IS_ARRAY) { - /* Array: [username, password] for AUTH2 */ - HashTable* ht = Z_ARRVAL_P(z_credentials); - zval* z_val = NULL; - int idx = 0; - char* username = NULL; - size_t username_len = 0; - char* password = NULL; - size_t password_len = 0; - - ZEND_HASH_FOREACH_VAL(ht, z_val) { - if (Z_TYPE_P(z_val) == IS_STRING) { - if (idx == 0) { - username = Z_STRVAL_P(z_val); - username_len = Z_STRLEN_P(z_val); - } else if (idx == 1) { - password = Z_STRVAL_P(z_val); - password_len = Z_STRLEN_P(z_val); - } - } - idx++; - } - ZEND_HASH_FOREACH_END(); - - if (password && arg_idx + 2 < 12) { - if (username) { - /* AUTH2 username password */ + /* Array: [password] for AUTH, or [username, password] for AUTH2 */ + HashTable* ht = Z_ARRVAL_P(z_credentials); + int num_elem = zend_hash_num_elements(ht); + zval* z_elem0 = zend_hash_index_find(ht, 0); + zval* z_elem1 = zend_hash_index_find(ht, 1); + + if (num_elem == 1 && z_elem0 && Z_TYPE_P(z_elem0) == IS_STRING) { + /* Single element: AUTH password */ + if (arg_idx + 2 <= 12) { core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; - core_args.args[arg_idx].data.string_arg.value = "AUTH2"; - core_args.args[arg_idx].data.string_arg.len = 5; + core_args.args[arg_idx].data.string_arg.value = "AUTH"; + core_args.args[arg_idx].data.string_arg.len = 4; arg_idx++; core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; - core_args.args[arg_idx].data.string_arg.value = username; - core_args.args[arg_idx].data.string_arg.len = username_len; + core_args.args[arg_idx].data.string_arg.value = Z_STRVAL_P(z_elem0); + core_args.args[arg_idx].data.string_arg.len = Z_STRLEN_P(z_elem0); arg_idx++; + } + } else if (num_elem >= 2 && z_elem0 && Z_TYPE_P(z_elem0) == IS_STRING && z_elem1 && + Z_TYPE_P(z_elem1) == IS_STRING) { + /* Two elements: AUTH2 username password */ + if (arg_idx + 3 <= 12) { core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; - core_args.args[arg_idx].data.string_arg.value = password; - core_args.args[arg_idx].data.string_arg.len = password_len; + core_args.args[arg_idx].data.string_arg.value = "AUTH2"; + core_args.args[arg_idx].data.string_arg.len = 5; arg_idx++; - } else { - /* AUTH password (array with single element) */ core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; - core_args.args[arg_idx].data.string_arg.value = "AUTH"; - core_args.args[arg_idx].data.string_arg.len = 4; + core_args.args[arg_idx].data.string_arg.value = Z_STRVAL_P(z_elem0); + core_args.args[arg_idx].data.string_arg.len = Z_STRLEN_P(z_elem0); arg_idx++; core_args.args[arg_idx].type = CORE_ARG_TYPE_STRING; - core_args.args[arg_idx].data.string_arg.value = password; - core_args.args[arg_idx].data.string_arg.len = password_len; + core_args.args[arg_idx].data.string_arg.value = Z_STRVAL_P(z_elem1); + core_args.args[arg_idx].data.string_arg.len = Z_STRLEN_P(z_elem1); arg_idx++; } } @@ -547,15 +533,16 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla all[arg_idx].data.string_arg.len = 4; arg_idx++; - /* Append individual keys */ + /* Append individual keys, converting non-string values to strings */ zval* z_val; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_key), z_val) { - if (Z_TYPE_P(z_val) == IS_STRING) { - all[arg_idx].type = CORE_ARG_TYPE_STRING; - all[arg_idx].data.string_arg.value = Z_STRVAL_P(z_val); - all[arg_idx].data.string_arg.len = Z_STRLEN_P(z_val); - arg_idx++; + if (Z_TYPE_P(z_val) != IS_STRING) { + convert_to_string(z_val); } + all[arg_idx].type = CORE_ARG_TYPE_STRING; + all[arg_idx].data.string_arg.value = Z_STRVAL_P(z_val); + all[arg_idx].data.string_arg.len = Z_STRLEN_P(z_val); + arg_idx++; } ZEND_HASH_FOREACH_END(); From 390abbce937141903858c815df0b4ed8bd9599e6 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Tue, 28 Jul 2026 16:50:30 -0700 Subject: [PATCH 26/37] Fix lint errors Signed-off-by: Prateek Kumar --- tests/ValkeyGlideTest.php | 4 ++-- valkey_glide_cluster.c | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index f9fbbee9..d3cfc204 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2744,7 +2744,7 @@ public function testBgRewriteAofBatch() $this->assertTrue($result[0]); } - public function testMigrateToInvalidHostReturnsFalse() + public function testMigrateToInvalidHostReturnsFalse() { $key = 'migrate_' . $this->createRandomString(); $this->valkey_glide->set($key, 'test_value'); @@ -3109,7 +3109,7 @@ public function testClientPauseBatch() $this->assertTrue($result[0]); $this->assertTrue($result[1]); } - + public function testReset() { $key = '{reset_test}_' . $this->createRandomString(); diff --git a/valkey_glide_cluster.c b/valkey_glide_cluster.c index 09d3272f..9b865d89 100644 --- a/valkey_glide_cluster.c +++ b/valkey_glide_cluster.c @@ -985,11 +985,12 @@ BGSAVE_METHOD_IMPL(ValkeyGlideCluster) BGREWRITEAOF_METHOD_IMPL(ValkeyGlideCluster) /* }}} */ -/* {{{ proto ValkeyGlideCluster::migrate(string host, int port, string key, int destinationDb, int timeout +/* {{{ proto ValkeyGlideCluster::migrate(string host, int port, string key, int destinationDb, int + * timeout * [, bool copy, bool replace, mixed credentials]) */ MIGRATE_METHOD_IMPL(ValkeyGlideCluster) /* }}} */ - + /* {{{ proto ValkeyGlideCluster::clientPause(int timeout [, string mode]) */ CLIENT_PAUSE_METHOD_IMPL(ValkeyGlideCluster) /* }}} */ From 62965056a49f3eeea9b03e4129b214a0f7e38cbb Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Tue, 28 Jul 2026 16:55:00 -0700 Subject: [PATCH 27/37] Fix lint errors Signed-off-by: Prateek Kumar --- valkey_glide.stub.php | 11 ++++++++++- valkey_glide_cluster.stub.php | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/valkey_glide.stub.php b/valkey_glide.stub.php index 32c6e418..f6f7512b 100644 --- a/valkey_glide.stub.php +++ b/valkey_glide.stub.php @@ -2247,7 +2247,16 @@ public function move(string $key, int $index): ValkeyGlide|bool; * * @see https://valkey.io/commands/migrate */ - public function migrate(string $host, int $port, string|array $key, int $destinationDb, int $timeout, bool $copy = false, bool $replace = false, mixed $credentials = null): ValkeyGlide|bool|string; + public function migrate( + string $host, + int $port, + string|array $key, + int $destinationDb, + int $timeout, + bool $copy = false, + bool $replace = false, + mixed $credentials = null + ): ValkeyGlide|bool|string; /** * Set one or more string keys. diff --git a/valkey_glide_cluster.stub.php b/valkey_glide_cluster.stub.php index 704e3365..57df8066 100644 --- a/valkey_glide_cluster.stub.php +++ b/valkey_glide_cluster.stub.php @@ -491,7 +491,16 @@ public function move(string $key, int $index): ValkeyGlideCluster|bool; * * Note: In cluster mode, only single-key migration is supported. */ - public function migrate(string $host, int $port, string $key, int $destinationDb, int $timeout, bool $copy = false, bool $replace = false, mixed $credentials = null): ValkeyGlideCluster|bool|string; + public function migrate( + string $host, + int $port, + string $key, + int $destinationDb, + int $timeout, + bool $copy = false, + bool $replace = false, + mixed $credentials = null + ): ValkeyGlideCluster|bool|string; /** * @see https://valkey.io/commands/copy From 379925f65a87ba8c6b89c04709d8067267e9d712 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Wed, 29 Jul 2026 10:12:27 -0700 Subject: [PATCH 28/37] Revert to to match PHPRedis named argument compatibility Signed-off-by: Prateek Kumar --- valkey_glide.stub.php | 4 ++-- valkey_glide_cluster.c | 2 +- valkey_glide_cluster.stub.php | 2 +- valkey_glide_commands.c | 8 ++++---- valkey_z_php_methods.c | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/valkey_glide.stub.php b/valkey_glide.stub.php index f6f7512b..728ff50f 100644 --- a/valkey_glide.stub.php +++ b/valkey_glide.stub.php @@ -2234,7 +2234,7 @@ public function move(string $key, int $index): ValkeyGlide|bool; * @param string $host The destination host. * @param int $port The destination port. * @param string|array $key The key to migrate, or an array of keys for multi-key migration. - * @param int $destinationDb The destination database index. + * @param int $dstdb The destination database index. * @param int $timeout The maximum idle time in milliseconds for the transfer. * @param bool $copy If true, do not remove the key from the source instance. * @param bool $replace If true, replace existing key on the destination instance. @@ -2251,7 +2251,7 @@ public function migrate( string $host, int $port, string|array $key, - int $destinationDb, + int $dstdb, int $timeout, bool $copy = false, bool $replace = false, diff --git a/valkey_glide_cluster.c b/valkey_glide_cluster.c index 9b865d89..515f66b3 100644 --- a/valkey_glide_cluster.c +++ b/valkey_glide_cluster.c @@ -985,7 +985,7 @@ BGSAVE_METHOD_IMPL(ValkeyGlideCluster) BGREWRITEAOF_METHOD_IMPL(ValkeyGlideCluster) /* }}} */ -/* {{{ proto ValkeyGlideCluster::migrate(string host, int port, string key, int destinationDb, int +/* {{{ proto ValkeyGlideCluster::migrate(string host, int port, string key, int dstdb, int * timeout * [, bool copy, bool replace, mixed credentials]) */ MIGRATE_METHOD_IMPL(ValkeyGlideCluster) diff --git a/valkey_glide_cluster.stub.php b/valkey_glide_cluster.stub.php index 57df8066..6de4ff2d 100644 --- a/valkey_glide_cluster.stub.php +++ b/valkey_glide_cluster.stub.php @@ -495,7 +495,7 @@ public function migrate( string $host, int $port, string $key, - int $destinationDb, + int $dstdb, int $timeout, bool $copy = false, bool $replace = false, diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index 4a69f212..7997efef 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -366,7 +366,7 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla size_t host_len = 0; long port; zval* z_key = NULL; - long destination_db; + long dstdb; long timeout; zend_bool copy = 0; zend_bool replace = 0; @@ -378,7 +378,7 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla return 0; } - /* Parse parameters: host, port, key (string|array), destinationDb, timeout, copy, replace, + /* Parse parameters: host, port, key (string|array), dstdb, timeout, copy, replace, * credentials */ if (zend_parse_method_parameters(argc, @@ -390,7 +390,7 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla &host_len, &port, &z_key, - &destination_db, + &dstdb, &timeout, ©, &replace, @@ -439,7 +439,7 @@ int execute_migrate_command(zval* object, int argc, zval* return_value, zend_cla /* arg[3]: destination db */ core_args.args[arg_idx].type = CORE_ARG_TYPE_LONG; - core_args.args[arg_idx].data.long_arg.value = destination_db; + core_args.args[arg_idx].data.long_arg.value = dstdb; arg_idx++; /* arg[4]: timeout */ diff --git a/valkey_z_php_methods.c b/valkey_z_php_methods.c index 806b0e32..0379a303 100644 --- a/valkey_z_php_methods.c +++ b/valkey_z_php_methods.c @@ -822,7 +822,7 @@ BGSAVE_METHOD_IMPL(ValkeyGlide) BGREWRITEAOF_METHOD_IMPL(ValkeyGlide) /* }}} */ -/* {{{ proto bool ValkeyGlide::migrate(string host, int port, string|array key, int destinationDb, +/* {{{ proto bool ValkeyGlide::migrate(string host, int port, string|array key, int dstdb, * int timeout [, bool copy, bool replace, mixed credentials]) */ MIGRATE_METHOD_IMPL(ValkeyGlide) /* }}} */ From a46eb184d4da321ab2e57b36f9b0cc572eab6a26 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Wed, 29 Jul 2026 10:17:46 -0700 Subject: [PATCH 29/37] Remove del() for random keys Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index a24b60b1..a9abced6 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -645,8 +645,6 @@ public function testMigrateToInvalidHostReturnsFalse() // Existing key + invalid host returns false (connection error) $result = $this->valkey_glide->migrate('nonexistent.invalid', 9999, $key, 0, 1000); $this->assertFalse($result); - - $this->valkey_glide->del($key); } public function testMigrateNonExistentKeyReturnsTrue() From ea95bafca7e3b379f75f35a2f778e9e71e455760 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Wed, 29 Jul 2026 10:26:05 -0700 Subject: [PATCH 30/37] Fix tests Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index a9abced6..3ec7af23 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -650,8 +650,8 @@ public function testMigrateToInvalidHostReturnsFalse() public function testMigrateNonExistentKeyReturnsTrue() { $result = $this->valkey_glide->migrate( - 'nonexistent.invalid', - 6379, + '127.0.0.1', + self::MIGRATE_DEST_PORT, 'nonexistent_key_' . uniqid(), 0, 1000 @@ -663,8 +663,8 @@ public function testMigrateNonExistentKeyWithReplyLiteral() { $this->withOptReplyLiteralEnabled(function () { $result = $this->valkey_glide->migrate( - 'nonexistent.invalid', - 6379, + '127.0.0.1', + self::MIGRATE_DEST_PORT, 'nonexistent_key_' . uniqid(), 0, 1000 From 66cbea71a5748c5e04ee16e4ad2b17123ec4d953 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Wed, 29 Jul 2026 10:30:28 -0700 Subject: [PATCH 31/37] Remove exists command in tests Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index 3ec7af23..2a884813 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -699,7 +699,6 @@ public function testMigrateWithCopyKeyRemainsAtSource() $this->assertTrue($result); // Key should remain at source and also exist at destination - $this->assertEquals(1, $this->valkey_glide->exists($key)); $this->assertEquals('copy_value', $this->valkey_glide->get($key)); $this->assertEquals('copy_value', $dest->get($key)); From 4ac362afe1cb128ef903442ecc8fb1983ae73640 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Wed, 29 Jul 2026 10:36:38 -0700 Subject: [PATCH 32/37] Update comments Signed-off-by: Prateek Kumar --- tests/ValkeyGlideTest.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index d3cfc204..5ceee041 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2965,13 +2965,13 @@ public function testMigrateBatchMultiKey() public function testMigrateWithAuthCredentials() { - // AUTH with string password - should not error (server may not require auth, - // but verifies the argument is correctly serialized and sent) + // Verifies the command does not error when AUTH credentials are provided. + // Does not verify credentials are correctly received by the destination. + // See: https://github.com/valkey-io/valkey-glide-php/issues/286 $key = 'migrate_auth_' . $this->createRandomString(); $this->valkey_glide->set($key, 'auth_value'); - // Migrate with AUTH password to invalid host - verifies credentials are passed correctly - // The connection error (false) confirms the command was built and sent, not rejected client-side + // Connection error (false) confirms the command was built and sent, not rejected client-side $result = $this->valkey_glide->migrate('nonexistent.invalid', 9999, $key, 0, 1000, false, false, 'mypassword'); $this->assertFalse($result); @@ -2980,11 +2980,12 @@ public function testMigrateWithAuthCredentials() public function testMigrateWithAuth2Credentials() { - // AUTH2 with [username, password] array - should not error + // Verifies the command does not error when AUTH2 credentials are provided. + // Does not verify credentials are correctly received by the destination. + // See: https://github.com/valkey-io/valkey-glide-php/issues/286 $key = 'migrate_auth2_' . $this->createRandomString(); $this->valkey_glide->set($key, 'auth2_value'); - // Migrate with AUTH2 credentials to invalid host - verifies credentials are passed correctly $result = $this->valkey_glide->migrate('nonexistent.invalid', 9999, $key, 0, 1000, false, false, ['myuser', 'mypassword']); $this->assertFalse($result); @@ -2993,7 +2994,8 @@ public function testMigrateWithAuth2Credentials() public function testMigrateWithAuth2SingleElementArray() { - // AUTH with single-element array [password] - should use AUTH (not AUTH2) + // Verifies single-element array [password] uses AUTH (not AUTH2) without error. + // See: https://github.com/valkey-io/valkey-glide-php/issues/286 $key = 'migrate_auth_arr_' . $this->createRandomString(); $this->valkey_glide->set($key, 'auth_arr_value'); From bc9a734245d3078cd2b0a79798b4a6518656ba1e Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Wed, 29 Jul 2026 10:41:37 -0700 Subject: [PATCH 33/37] Updated doc Signed-off-by: Prateek Kumar --- valkey_glide.stub.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/valkey_glide.stub.php b/valkey_glide.stub.php index 728ff50f..b2cfa9d8 100644 --- a/valkey_glide.stub.php +++ b/valkey_glide.stub.php @@ -2226,9 +2226,9 @@ public function mget(array $keys): ValkeyGlide|array|false; public function move(string $key, int $index): ValkeyGlide|bool; /** - * Atomically transfer a key from the connected instance to a destination instance. + * Atomically transfers one or more keys from the connected instance to a destination instance. * - * On success, the key is deleted from the source instance (unless COPY is set). + * On success, the keys are deleted from the source instance (unless COPY is set). * The command is atomic and blocks both instances during the transfer. * * @param string $host The destination host. @@ -2236,8 +2236,8 @@ public function move(string $key, int $index): ValkeyGlide|bool; * @param string|array $key The key to migrate, or an array of keys for multi-key migration. * @param int $dstdb The destination database index. * @param int $timeout The maximum idle time in milliseconds for the transfer. - * @param bool $copy If true, do not remove the key from the source instance. - * @param bool $replace If true, replace existing key on the destination instance. + * @param bool $copy If true, do not remove the key(s) from the source instance. + * @param bool $replace If true, replace existing key(s) on the destination instance. * @param mixed $credentials Optional authentication: a string password, or * an array [username, password] for AUTH2. * From 683b12e04c98a82941b6c4ac75cb1119e67e1f8b Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Wed, 29 Jul 2026 10:46:00 -0700 Subject: [PATCH 34/37] Fix tests Signed-off-by: Prateek Kumar --- tests/ValkeyGlideTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 5ceee041..edefead3 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2760,8 +2760,8 @@ public function testMigrateNonExistentKeyReturnsTrue() { // Non-existent key returns true (NOKEY is treated as success without literal) $result = $this->valkey_glide->migrate( - 'nonexistent.invalid', - 6379, + '127.0.0.1', + self::MIGRATE_DEST_PORT, 'nonexistent_key_' . $this->createRandomString(), 0, 1000 @@ -2773,8 +2773,8 @@ public function testMigrateMultiKeyNonExistentReturnsTrue() { // Multi non-existent keys returns true (NOKEY) $result = $this->valkey_glide->migrate( - 'nonexistent.invalid', - 6379, + '127.0.0.1', + self::MIGRATE_DEST_PORT, ['nonexistent_key1_' . $this->createRandomString(), 'nonexistent_key2_' . $this->createRandomString()], 0, 1000 @@ -2799,8 +2799,8 @@ public function testMigrateNonExistentKeyWithReplyLiteral() { $this->withOptReplyLiteralEnabled(function () { $result = $this->valkey_glide->migrate( - 'nonexistent.invalid', - 6379, + '127.0.0.1', + self::MIGRATE_DEST_PORT, 'nonexistent_key_' . $this->createRandomString(), 0, 1000 @@ -2928,8 +2928,8 @@ public function testMigrateBatch() $this->valkey_glide->pipeline(); $this->valkey_glide->migrate( - 'nonexistent.invalid', - 6379, + '127.0.0.1', + self::MIGRATE_DEST_PORT, 'nonexistent_key_' . $this->createRandomString(), 0, 1000 From 441a10e3385e5e77d7da5fd54bea45625a86b4ea Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Wed, 29 Jul 2026 10:54:29 -0700 Subject: [PATCH 35/37] Fix tests Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 9 +++------ tests/ValkeyGlideTest.php | 9 +++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index 2a884813..95a3b45a 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -712,11 +712,8 @@ public function testMigrateWithReplaceOverwritesDestination() $key = '{migrate_replace}_' . uniqid(); $this->valkey_glide->set($key, 'source_value'); - // First migrate with COPY to create key at destination - $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000, true); - - // Set a new value at source - $this->valkey_glide->set($key, 'new_source_value'); + // Set existing value directly on destination + $dest->set($key, 'old_dest_value'); // Migrate with REPLACE - should overwrite destination $result = $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000, false, true); @@ -724,7 +721,7 @@ public function testMigrateWithReplaceOverwritesDestination() // Key should be removed from source, destination has new value $this->assertEquals(0, $this->valkey_glide->exists($key)); - $this->assertEquals('new_source_value', $dest->get($key)); + $this->assertEquals('source_value', $dest->get($key)); } public function testMigrateBatch() diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index edefead3..a2b1b4b4 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2904,11 +2904,8 @@ public function testMigrateWithReplaceOverwritesDestination() $key = 'migrate_replace_' . $this->createRandomString(); $this->valkey_glide->set($key, 'source_value'); - // First migrate with COPY to create key at destination - $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000, true); - - // Set a new value at source - $this->valkey_glide->set($key, 'new_source_value'); + // Set existing value directly on destination + $dest->set($key, 'old_dest_value'); // Migrate with REPLACE - should overwrite destination $result = $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000, false, true); @@ -2916,7 +2913,7 @@ public function testMigrateWithReplaceOverwritesDestination() // Key should be removed from source, destination has new value $this->assertEquals(0, $this->valkey_glide->exists($key)); - $this->assertEquals('new_source_value', $dest->get($key)); + $this->assertEquals('source_value', $dest->get($key)); } public function testMigrateBatch() From 100f017df548f9565246470d39070e9b196ea541 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Wed, 29 Jul 2026 11:02:51 -0700 Subject: [PATCH 36/37] Fix test Signed-off-by: Prateek Kumar --- tests/ValkeyGlideTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index a2b1b4b4..cf71ece5 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -2855,7 +2855,7 @@ public function testMigrateMultiKeyManyKeys() $keys[] = $key; } - // Migrate with COPY + REPLACE — args: host, port, "", db, timeout, COPY, REPLACE, KEYS, key0..key9 + // Migrate with COPY + REPLACE to exercise dynamic allocation (total args > 12) $result = $this->valkey_glide->migrate( '127.0.0.1', self::MIGRATE_DEST_PORT, From 8ce99a9bec22f53b0dddb472d92828b2b07d9592 Mon Sep 17 00:00:00 2001 From: Prateek Kumar Date: Thu, 30 Jul 2026 09:50:41 -0700 Subject: [PATCH 37/37] Remove duplicate tests Signed-off-by: Prateek Kumar --- tests/ValkeyGlideClusterTest.php | 106 ------------------------------- 1 file changed, 106 deletions(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index 95a3b45a..040d0980 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -637,112 +637,6 @@ public function testBgRewriteAofBatch() } } - public function testMigrateToInvalidHostReturnsFalse() - { - $key = '{migrate_test}_' . uniqid(); - $this->valkey_glide->set($key, 'test_value'); - - // Existing key + invalid host returns false (connection error) - $result = $this->valkey_glide->migrate('nonexistent.invalid', 9999, $key, 0, 1000); - $this->assertFalse($result); - } - - public function testMigrateNonExistentKeyReturnsTrue() - { - $result = $this->valkey_glide->migrate( - '127.0.0.1', - self::MIGRATE_DEST_PORT, - 'nonexistent_key_' . uniqid(), - 0, - 1000 - ); - $this->assertTrue($result); - } - - public function testMigrateNonExistentKeyWithReplyLiteral() - { - $this->withOptReplyLiteralEnabled(function () { - $result = $this->valkey_glide->migrate( - '127.0.0.1', - self::MIGRATE_DEST_PORT, - 'nonexistent_key_' . uniqid(), - 0, - 1000 - ); - $this->assertEquals('NOKEY', $result); - }); - } - - public function testMigrateSingleKeySuccess() - { - $dest = $this->getMigrateDestClient(); - - $key = '{migrate_success}_' . uniqid(); - $this->valkey_glide->set($key, 'migrate_value'); - - $result = $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000); - $this->assertTrue($result); - - // Key should be removed from source and exist at destination - $this->assertEquals(0, $this->valkey_glide->exists($key)); - $this->assertEquals('migrate_value', $dest->get($key)); - } - - public function testMigrateWithCopyKeyRemainsAtSource() - { - $dest = $this->getMigrateDestClient(); - - $key = '{migrate_copy}_' . uniqid(); - $this->valkey_glide->set($key, 'copy_value'); - - $result = $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000, true); - $this->assertTrue($result); - - // Key should remain at source and also exist at destination - $this->assertEquals('copy_value', $this->valkey_glide->get($key)); - $this->assertEquals('copy_value', $dest->get($key)); - - $this->valkey_glide->del($key); - } - - public function testMigrateWithReplaceOverwritesDestination() - { - $dest = $this->getMigrateDestClient(); - - $key = '{migrate_replace}_' . uniqid(); - $this->valkey_glide->set($key, 'source_value'); - - // Set existing value directly on destination - $dest->set($key, 'old_dest_value'); - - // Migrate with REPLACE - should overwrite destination - $result = $this->valkey_glide->migrate('127.0.0.1', self::MIGRATE_DEST_PORT, $key, 0, 5000, false, true); - $this->assertTrue($result); - - // Key should be removed from source, destination has new value - $this->assertEquals(0, $this->valkey_glide->exists($key)); - $this->assertEquals('source_value', $dest->get($key)); - } - - public function testMigrateBatch() - { - if (!$this->havePipeline()) { - $this->markTestSkipped('Pipeline not supported'); - return; - } - - $this->valkey_glide->pipeline(); - $this->valkey_glide->migrate( - 'nonexistent.invalid', - 6379, - 'nonexistent_key_' . uniqid(), - 0, - 1000 - ); - $result = $this->valkey_glide->exec(); - $this->assertTrue($result[0]); - } - public function testMigrateMultiKeyNotSupported() { $this->markTestSkipped('Multi-key migration not supported in cluster mode');