diff --git a/CHANGELOG.md b/CHANGELOG.md index 10d5cd3c..93ed941c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Changes +* Add `MIGRATE` command for standalone and cluster clients ([#276](https://github.com/valkey-io/valkey-glide-php/pull/276)) * Add `CLIENT PAUSE` and `CLIENT UNPAUSE` commands for standalone and cluster clients ([#274](https://github.com/valkey-io/valkey-glide-php/pull/274)) * 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)) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index 6d9d3e7c..040d0980 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -637,6 +637,36 @@ public function testBgRewriteAofBatch() } } + public function testMigrateMultiKeyNotSupported() + { + $this->markTestSkipped('Multi-key migration not supported in cluster mode'); + } + + public function testMigrateMultiKeyNonExistentReturnsTrue() + { + $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 testMigrateMultiKeyManyKeys() + { + $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 testClientPause() { $key = '{client_pause_all}_' . uniqid(); diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 96b4f016..cf71ece5 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -88,6 +88,29 @@ private function createRandomString($length = 10) return bin2hex(random_bytes(ceil($length / 2))); } + /** + * Port for the destination Valkey server used in migrate tests. + * See also "tests/start_valkey_with_replicas.sh". + */ + protected const MIGRATE_DEST_PORT = 6382; + + /** + * Shared destination client for migrate tests. + */ + protected ?ValkeyGlide $migrateDestClient = null; + + protected function getMigrateDestClient(): ValkeyGlide + { + 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; + } + /** * Execute a callable with OPT_REPLY_LITERAL enabled, ensuring it is * always disabled afterwards even if an exception is thrown. @@ -2721,6 +2744,264 @@ public function testBgRewriteAofBatch() $this->assertTrue($result[0]); } + public function testMigrateToInvalidHostReturnsFalse() + { + $key = 'migrate_' . $this->createRandomString(); + $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); + + $this->valkey_glide->del($key); + } + + public function testMigrateNonExistentKeyReturnsTrue() + { + // Non-existent key returns true (NOKEY is treated as success without literal) + $result = $this->valkey_glide->migrate( + '127.0.0.1', + self::MIGRATE_DEST_PORT, + 'nonexistent_key_' . $this->createRandomString(), + 0, + 1000 + ); + $this->assertTrue($result); + } + + public function testMigrateMultiKeyNonExistentReturnsTrue() + { + // Multi non-existent keys returns true (NOKEY) + $result = $this->valkey_glide->migrate( + '127.0.0.1', + self::MIGRATE_DEST_PORT, + ['nonexistent_key1_' . $this->createRandomString(), 'nonexistent_key2_' . $this->createRandomString()], + 0, + 1000 + ); + $this->assertTrue($result); + } + + 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 testMigrateNonExistentKeyWithReplyLiteral() + { + $this->withOptReplyLiteralEnabled(function () { + $result = $this->valkey_glide->migrate( + '127.0.0.1', + self::MIGRATE_DEST_PORT, + 'nonexistent_key_' . $this->createRandomString(), + 0, + 1000 + ); + $this->assertEquals('NOKEY', $result); + }); + } + + public function testMigrateSingleKeySuccess() + { + $dest = $this->getMigrateDestClient(); + + $key = 'migrate_success_' . $this->createRandomString(); + $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 testMigrateMultiKeySuccess() + { + $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', self::MIGRATE_DEST_PORT, [$key1, $key2], 0, 5000); + $this->assertTrue($result); + + // Keys should be removed from source and exist at destination + $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 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 to exercise dynamic allocation (total args > 12) + $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(); + + $key = 'migrate_copy_' . $this->createRandomString(); + $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(1, $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); + } + + public function testMigrateWithReplaceOverwritesDestination() + { + $dest = $this->getMigrateDestClient(); + + $key = 'migrate_replace_' . $this->createRandomString(); + $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( + '127.0.0.1', + self::MIGRATE_DEST_PORT, + 'nonexistent_key_' . $this->createRandomString(), + 0, + 1000 + ); + $result = $this->valkey_glide->exec(); + $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() + { + // 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'); + + // 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() + { + // 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'); + + $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() + { + // 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'); + + $result = $this->valkey_glide->migrate('nonexistent.invalid', 9999, $key, 0, 1000, false, false, ['mypassword']); + $this->assertFalse($result); + + $this->valkey_glide->del($key); + } + public function testClientPause() { $key = 'client_pause_all_' . $this->createRandomString(); diff --git a/tests/start_valkey_with_replicas.sh b/tests/start_valkey_with_replicas.sh index f3c23e0d..7e442532 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,15 @@ 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 0.0.0.0 \ + --dir "$BASE_DIR/6382" \ + --daemonize yes \ + --logfile "$BASE_DIR/6382/valkey.log" \ + --enable-debug-command yes \ + --protected-mode no + # 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 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.stub.php b/valkey_glide.stub.php index fc6e64ba..b2cfa9d8 100644 --- a/valkey_glide.stub.php +++ b/valkey_glide.stub.php @@ -2225,6 +2225,39 @@ public function mget(array $keys): ValkeyGlide|array|false; */ public function move(string $key, int $index): ValkeyGlide|bool; + /** + * Atomically transfers one or more keys from the connected instance to a destination instance. + * + * 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. + * @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(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. + * + * @return ValkeyGlide|bool|string Returns true on success, false on failure. + * With OPT_REPLY_LITERAL enabled, returns "OK" or "NOKEY" on success, + * false on failure. + * + * @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 37856d20..515f66b3 100644 --- a/valkey_glide_cluster.c +++ b/valkey_glide_cluster.c @@ -985,6 +985,12 @@ 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::clientPause(int timeout [, string mode]) */ CLIENT_PAUSE_METHOD_IMPL(ValkeyGlideCluster) /* }}} */ diff --git a/valkey_glide_cluster.stub.php b/valkey_glide_cluster.stub.php index 4068048b..6de4ff2d 100644 --- a/valkey_glide_cluster.stub.php +++ b/valkey_glide_cluster.stub.php @@ -486,6 +486,22 @@ public function select(int $db): ValkeyGlideCluster|bool; */ 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; + /** * @see https://valkey.io/commands/copy */ diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index c98b545e..7997efef 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,7 +359,217 @@ 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 SAVE 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 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: 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; + } 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 (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++; + 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: [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 = "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 = 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 = "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 = 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 = Z_STRVAL_P(z_elem1); + core_args.args[arg_idx].data.string_arg.len = Z_STRLEN_P(z_elem1); + arg_idx++; + } + } + } + } + + /* 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)); + 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)); + + /* Copy existing fixed args into the combined array */ + memcpy(all, core_args.args, arg_idx * sizeof(core_arg_t)); + + /* Append KEYS keyword */ + 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, 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) { + 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(); + + core_args.all_args = all; + } + + 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; + + int result = + execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object); + + /* Free dynamic args if allocated */ + if (core_args.all_args) { + efree(core_args.all_args); + } + + return result; +} + +/* 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; @@ -400,7 +610,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; @@ -432,7 +642,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 CLIENT PAUSE command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a CLIENT PAUSE command using the Valkey Glide client */ int execute_client_pause_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; long timeout; @@ -478,7 +688,7 @@ int execute_client_pause_command(zval* object, int argc, zval* return_value, zen return execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object); } -/* Execute a CLIENT UNPAUSE command using the Valkey Glide client - UNIFIED IMPLEMENTATION */ +/* Execute a CLIENT UNPAUSE command using the Valkey Glide client */ int execute_client_unpause_command(zval* object, int argc, zval* return_value, @@ -510,7 +720,7 @@ int execute_client_unpause_command(zval* object, 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; @@ -548,7 +758,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; @@ -594,7 +804,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; @@ -1023,7 +1233,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; @@ -1074,7 +1284,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_commands_common.h b/valkey_glide_commands_common.h index 1cfbbf12..f0ddd803 100644 --- a/valkey_glide_commands_common.h +++ b/valkey_glide_commands_common.h @@ -206,6 +206,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_client_pause_command(zval* object, int argc, zval* return_value, zend_class_entry* ce); int execute_client_unpause_command(zval* object, int argc, @@ -484,6 +485,9 @@ int execute_unlink_command(zval* object, int argc, zval* return_value, zend_clas 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_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; diff --git a/valkey_glide_core_common.c b/valkey_glide_core_common.c index e86f6b2a..2782203f 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 Echo: case FlushAll: case FlushDB: + case Migrate: case Ping: case Select: case SwapDb: @@ -644,11 +645,14 @@ int prepare_message_args(core_command_args_t* args, return 0; } - /* Calculate total argument count - just the arguments, no key */ + /* 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) { + switch (arg_array[i].type) { case CORE_ARG_TYPE_STRING: case CORE_ARG_TYPE_LONG: case CORE_ARG_TYPE_DOUBLE: @@ -669,18 +673,18 @@ int prepare_message_args(core_command_args_t* args, int arg_idx = 0; - /* Add all arguments */ + /* Add all arguments from the single array */ for (int i = 0; i < args->arg_count; i++) { - switch (args->args[i].type) { + switch (arg_array[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; + (*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->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; @@ -692,7 +696,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->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 5e6f7c33..02d9a2f4 100644 --- a/valkey_glide_core_common.h +++ b/valkey_glide_core_common.h @@ -99,13 +99,14 @@ 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[8]; /* Fixed arguments array - sufficient for current usage */ + 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; zend_bool is_cluster; /* Flag to indicate cluster mode */ diff --git a/valkey_z_php_methods.c b/valkey_z_php_methods.c index 3b50da5f..0379a303 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 bool ValkeyGlide::clientPause(int timeout [, string mode]) */ CLIENT_PAUSE_METHOD_IMPL(ValkeyGlide) /* }}} */