Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
89de2f5
Implement migrate command
prateek-kumar-improving Jul 22, 2026
e9e8846
Implement migrate command
prateek-kumar-improving Jul 22, 2026
15ec443
Implement migrate command
prateek-kumar-improving Jul 22, 2026
791c80e
Fix CI lint errors
prateek-kumar-improving Jul 22, 2026
3795285
Fix tests
prateek-kumar-improving Jul 22, 2026
7e25871
Fix tests
prateek-kumar-improving Jul 22, 2026
359bba4
Fix tests
prateek-kumar-improving Jul 22, 2026
1da312c
Refactor based on previous PR reviews
prateek-kumar-improving Jul 27, 2026
921d3e2
Merge branch 'main' into implement-migrate-command
prateek-kumar-improving Jul 27, 2026
eed267a
Refactor based on previous PR reviews
prateek-kumar-improving Jul 27, 2026
bb23b75
Refactor based on previous PR reviews
prateek-kumar-improving Jul 27, 2026
0cc68e8
fix tests
prateek-kumar-improving Jul 27, 2026
11c50fe
fix tests
prateek-kumar-improving Jul 27, 2026
6444774
fix tests
prateek-kumar-improving Jul 27, 2026
b7912fc
fix tests
prateek-kumar-improving Jul 27, 2026
d2a320e
fix tests
prateek-kumar-improving Jul 27, 2026
08a6c3c
fix tests
prateek-kumar-improving Jul 27, 2026
462e106
fix tests
prateek-kumar-improving Jul 27, 2026
ca1ecb8
Fix issues
prateek-kumar-improving Jul 27, 2026
2c1ce7d
Fix issues
prateek-kumar-improving Jul 27, 2026
b71b8e9
Fix variable name and add documentation
prateek-kumar-improving Jul 28, 2026
c9aa25a
Fix variable name
prateek-kumar-improving Jul 28, 2026
fbb982b
Fix documentation
prateek-kumar-improving Jul 28, 2026
572783f
Fix test name
prateek-kumar-improving Jul 28, 2026
991f1f1
Fix review comments
prateek-kumar-improving Jul 28, 2026
3ffa4e0
Fix review comments
prateek-kumar-improving Jul 28, 2026
b5e0d3d
Merge branch 'main' into implement-migrate-command
prateek-kumar-improving Jul 28, 2026
390abbc
Fix lint errors
prateek-kumar-improving Jul 28, 2026
6296505
Fix lint errors
prateek-kumar-improving Jul 28, 2026
379925f
Revert to to match PHPRedis named argument compatibility
prateek-kumar-improving Jul 29, 2026
a46eb18
Remove del() for random keys
prateek-kumar-improving Jul 29, 2026
ea95baf
Fix tests
prateek-kumar-improving Jul 29, 2026
66cbea7
Remove exists command in tests
prateek-kumar-improving Jul 29, 2026
4ac362a
Update comments
prateek-kumar-improving Jul 29, 2026
bc9a734
Updated doc
prateek-kumar-improving Jul 29, 2026
683b12e
Fix tests
prateek-kumar-improving Jul 29, 2026
441a10e
Fix tests
prateek-kumar-improving Jul 29, 2026
100f017
Fix test
prateek-kumar-improving Jul 29, 2026
8ce99a9
Remove duplicate tests
prateek-kumar-improving Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
30 changes: 30 additions & 0 deletions tests/ValkeyGlideClusterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Comment thread
prateek-kumar-improving marked this conversation as resolved.

public function testClientPause()
{
$key = '{client_pause_all}_' . uniqid();
Expand Down
281 changes: 281 additions & 0 deletions tests/ValkeyGlideTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment thread
currantw marked this conversation as resolved.

/**
* 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.
Expand Down Expand Up @@ -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()
Comment thread
currantw marked this conversation as resolved.
{
$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()
Comment thread
currantw marked this conversation as resolved.
{
// 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();
Expand Down
11 changes: 10 additions & 1 deletion tests/start_valkey_with_replicas.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 \
Comment thread
Aryex marked this conversation as resolved.
--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
Expand Down
2 changes: 1 addition & 1 deletion tests/stop-servers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading
Loading