Skip to content

Commit e549f92

Browse files
authored
Support SELECT, COPY, and MOVE commands in cluster (#61)
* Support SELECT, COPY, and MOVE commands in cluster mode. * Allow specifying the target database with COPY commands. * Re-enable SELECT tests that were disabled. * Disallow SELECT to be used in batch mode with a graceful error message. Signed-off-by: James Duong <[email protected]>
1 parent a470477 commit e549f92

15 files changed

+552
-138
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
#### Changes
44

5-
* PHP: Add Multi-Database Support for Cluster Mode Valkey 9.0 - Added `database_id` parameter to `ValkeyGlideCluster` constructor to enable database selection in cluster mode. This feature requires Valkey 9.0+ with `cluster-databases > 1` configuration.
5+
* PHP: Add Multi-Database Support for Cluster Mode Valkey 9.0 - Added `database_id` parameter to `ValkeyGlideCluster` constructor and support for SELECT, COPY, and MOVE commands in cluster mode. The COPY command can now specify a `database_id` parameter for cross-database operations. This feature requires Valkey 9.0+ with `cluster-databases > 1` configuration.
66

77
#### Documentation
88

tests/ValkeyGlideBatchTest.php

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,8 @@ public function testDatabaseOperationsBatch()
346346
{
347347
$key1 = 'batch_db_' . uniqid();
348348

349-
// Execute SELECT, DBSIZE, TYPE in multi/exec batch
349+
// Execute FLUSHDB, SET, DBSIZE, TYPE in multi/exec batch
350350
$results = $this->valkey_glide->multi()
351-
// ->select(0) // Select database 0 (likely current) //TODO return once select is supported
352351
->flushDB()
353352
->set('x', 'y')
354353
->set($key1, 'test_value')
@@ -359,8 +358,10 @@ public function testDatabaseOperationsBatch()
359358
// Verify transaction results
360359
$this->assertIsArray($results);
361360
$this->assertCount(5, $results);
362-
//$this->assertTrue($results[0]); // SELECT result
363-
$this->assertEquals(2, $results[3]);
361+
$this->assertTrue($results[0]); // FLUSHDB result
362+
$this->assertTrue($results[1]); // SET result
363+
$this->assertTrue($results[2]); // SET result
364+
$this->assertEquals(2, $results[3]); // DBSIZE result
364365
$this->assertEquals(ValkeyGlide::VALKEY_GLIDE_STRING, $results[4]); // TYPE result
365366

366367
// Verify server-side effects
@@ -1409,27 +1410,23 @@ public function testCopyDumpRestoreBatch()
14091410

14101411
public function testMoveBatch()
14111412
{
1412-
//TODO return once select is supported
1413-
$this->markTestSkipped();
14141413
$key1 = 'batch_move_' . uniqid();
14151414

14161415
// Setup test data in database 0
14171416
$this->valkey_glide->select(0);
14181417
$this->valkey_glide->set($key1, 'move_test_value');
14191418

1420-
// Execute MOVE, SELECT, EXISTS in multi/exec batch
1419+
// Execute MOVE, EXISTS in multi/exec batch
14211420
$results = $this->valkey_glide->multi()
14221421
->move($key1, 1) // Move to database 1
1423-
->select(1)
1424-
->exists($key1)
1422+
->exists($key1) // Check if key still exists in current db
14251423
->exec();
14261424

14271425
// Verify transaction results
14281426
$this->assertIsArray($results);
1429-
$this->assertCount(3, $results);
1427+
$this->assertCount(2, $results);
14301428
$this->assertTrue($results[0]); // MOVE result (success)
1431-
$this->assertTrue($results[1]); // SELECT result
1432-
$this->assertEquals(1, $results[2]); // EXISTS result (key exists in db 1)
1429+
$this->assertEquals(0, $results[1]); // EXISTS result (key no longer in db 0)
14331430

14341431
// Verify server-side effects
14351432
$this->valkey_glide->select(0);
@@ -3887,6 +3884,35 @@ public function testFunctionDumpRestoreBatch()
38873884
$this->valkey_glide->function('FLUSH');
38883885
}
38893886

3887+
// ===================================================================
3888+
// SELECT COMMAND BATCH MODE PREVENTION TESTS
3889+
// ===================================================================
3890+
3891+
public function testSelectFailsInBatchMode()
3892+
{
3893+
$key1 = 'batch_select_test_' . uniqid();
3894+
3895+
// Verify SELECT works in normal mode
3896+
$result = $this->valkey_glide->select(0);
3897+
$this->assertTrue($result, 'SELECT should work in normal mode');
3898+
3899+
// Test SELECT returns false in batch mode and logs error
3900+
$this->valkey_glide->multi();
3901+
$this->valkey_glide->set($key1, 'test_value');
3902+
3903+
// Capture output to check for error message
3904+
ob_start();
3905+
$selectResult = $this->valkey_glide->select(1);
3906+
$output = ob_get_clean();
3907+
3908+
$this->assertFalse($selectResult, 'SELECT should return false in batch mode');
3909+
$this->assertStringContains('Error: SELECT command cannot be used in batch mode', $output);
3910+
3911+
// Cancel the batch and cleanup
3912+
$this->valkey_glide->discard();
3913+
$this->valkey_glide->del($key1);
3914+
}
3915+
38903916
// ===================================================================
38913917
// CLOSING CLASS
38923918
// ===================================================================

tests/ValkeyGlideClusterTest.php

Lines changed: 162 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,18 +116,30 @@ public function testWait()
116116
{
117117
$this->markTestSkipped();
118118
}
119+
119120
public function testSelect()
120121
{
121-
$this->markTestSkipped();
122+
$this->assertFalse(@$this->valkey_glide->select(-1));
123+
$this->assertTrue($this->valkey_glide->select(0));
122124
}
123-
public function testReconnectSelect()
125+
126+
public function testMove()
124127
{
125-
$this->markTestSkipped();
128+
// Basic MOVE method availability test
129+
$key = '{key}test_move_' . uniqid();
130+
$this->valkey_glide->set($key, 'test_value');
131+
132+
// MOVE should return boolean (may be false if multi-database not supported)
133+
$result = $this->valkey_glide->move($key, 1);
134+
$this->assertIsBool($result);
135+
136+
// Clean up
137+
$this->valkey_glide->del($key);
126138
}
127139

128-
public function testMove()
140+
public function testReconnectSelect()
129141
{
130-
$this->markTestSkipped(); // Move is not supported in ValkeyGlideCluster
142+
$this->markTestSkipped();
131143
}
132144

133145
/* These 'directed node' commands work differently in ValkeyGlideCluster */
@@ -199,6 +211,14 @@ protected function newInstance()
199211
false, // use_tls
200212
$this->getAuth(), // credentials
201213
ValkeyGlide::READ_FROM_PRIMARY, // read_from
214+
null, // request_timeout
215+
null, // reconnect_strategy
216+
null, // client_name
217+
null, // periodic_checks
218+
null, // client_az
219+
null, // advanced_config
220+
null, // lazy_connect
221+
0 // database_id - enable multi-database support
202222
);
203223
} catch (Exception $ex) {
204224
TestSuite::errorMessage("Fatal error: %s\n", $ex->getMessage());
@@ -905,4 +925,141 @@ public function testReplyLiteral()
905925
// Reset
906926
$this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, false);
907927
}
928+
929+
public function testCopyCluster()
930+
{
931+
if (version_compare($this->version, '6.2.0') < 0) {
932+
$this->markTestSkipped('COPY command requires Valkey 6.2.0+');
933+
}
934+
935+
$this->valkey_glide->del('{key}dst');
936+
$this->valkey_glide->set('{key}src', 'foo');
937+
$this->assertTrue($this->valkey_glide->copy('{key}src', '{key}dst'));
938+
$this->assertKeyEquals('foo', '{key}dst');
939+
940+
$this->valkey_glide->set('{key}src', 'bar');
941+
$this->assertFalse($this->valkey_glide->copy('{key}src', '{key}dst'));
942+
$this->assertKeyEquals('foo', '{key}dst');
943+
944+
$this->assertTrue($this->valkey_glide->copy('{key}src', '{key}dst', ['REPLACE' => true]));
945+
$this->assertKeyEquals('bar', '{key}dst');
946+
}
947+
948+
public function testCopyClusterWithDatabase()
949+
{
950+
if (version_compare($this->version, '9.0.0') < 0) {
951+
$this->markTestSkipped('COPY with database ID in cluster mode requires Valkey 9.0.0+');
952+
}
953+
954+
// Test copy to different database in cluster mode
955+
$this->valkey_glide->del('{key}src', '{key}dst');
956+
$this->valkey_glide->set('{key}src', 'cluster_test_value');
957+
958+
// Test with string key
959+
$this->assertTrue($this->valkey_glide->copy('{key}src', '{key}dst', ['DB' => 1]));
960+
961+
// Test with constant
962+
$this->valkey_glide->set('{key}src2', 'cluster_constant_test');
963+
$this->assertTrue($this->valkey_glide->copy('{key}src2', '{key}dst2', [ValkeyGlide::COPY_DB => 1]));
964+
965+
// Test combined options
966+
$this->assertTrue($this->valkey_glide->copy('{key}src', '{key}dst', [
967+
ValkeyGlide::COPY_DB => 1,
968+
ValkeyGlide::COPY_REPLACE => true
969+
]));
970+
}
971+
972+
public function testSelectMultipleDatabase()
973+
{
974+
if (version_compare($this->version, '9.0.0') < 0) {
975+
$this->markTestSkipped('Multi-database operations in cluster mode require Valkey 9.0.0+');
976+
}
977+
978+
// SELECT should work in Valkey 9.0+ clusters
979+
$this->assertTrue($this->valkey_glide->select(0));
980+
$this->assertTrue($this->valkey_glide->select(1));
981+
$this->assertTrue($this->valkey_glide->select(2));
982+
$this->assertTrue($this->valkey_glide->select(15));
983+
$this->assertFalse(@$this->valkey_glide->select(-1));
984+
$this->assertTrue($this->valkey_glide->select(0));
985+
}
986+
987+
public function testDatabaseIsolation()
988+
{
989+
if (version_compare($this->version, '9.0.0') < 0) {
990+
$this->markTestSkipped('Multi-database operations in cluster mode require Valkey 9.0.0+');
991+
}
992+
993+
$key = '{key}isolation_test_' . uniqid();
994+
995+
$this->valkey_glide->select(0);
996+
$this->valkey_glide->set($key, 'value_db0');
997+
$this->valkey_glide->select(1);
998+
$this->valkey_glide->set($key, 'value_db1');
999+
1000+
$this->valkey_glide->select(0);
1001+
$this->assertEquals('value_db0', $this->valkey_glide->get($key));
1002+
$this->valkey_glide->select(1);
1003+
$this->assertEquals('value_db1', $this->valkey_glide->get($key));
1004+
1005+
// Clean up
1006+
$this->valkey_glide->del($key);
1007+
$this->valkey_glide->select(0);
1008+
$this->valkey_glide->del($key);
1009+
}
1010+
1011+
public function testMoveMultiDatabase()
1012+
{
1013+
if (version_compare($this->version, '9.0.0') < 0) {
1014+
$this->markTestSkipped('Multi-database MOVE in cluster mode requires Valkey 9.0.0+');
1015+
}
1016+
1017+
$key = '{key}move_test_' . uniqid();
1018+
1019+
$this->valkey_glide->select(0);
1020+
$this->valkey_glide->set($key, 'move_test_value');
1021+
1022+
// In Valkey 9.0+, MOVE should succeed - failure indicates missing cluster-databases config
1023+
$result = $this->valkey_glide->move($key, 1);
1024+
$this->assertTrue($result, 'MOVE should succeed in Valkey 9.0+ cluster (ensure cluster-databases > 1 is configured)');
1025+
1026+
// Verify MOVE worked correctly
1027+
$this->assertEquals(0, $this->valkey_glide->exists($key)); // Should not exist in DB 0
1028+
1029+
$this->valkey_glide->select(1);
1030+
$this->assertEquals(1, $this->valkey_glide->exists($key)); // Should exist in DB 1
1031+
$this->assertEquals('move_test_value', $this->valkey_glide->get($key));
1032+
1033+
// Clean up
1034+
$this->valkey_glide->del($key);
1035+
$this->valkey_glide->select(0);
1036+
}
1037+
1038+
public function testCopyMultiDatabase()
1039+
{
1040+
if (version_compare($this->version, '9.0.0') < 0) {
1041+
$this->markTestSkipped('Multi-database COPY in cluster mode requires Valkey 9.0.0+');
1042+
}
1043+
1044+
$srcKey = '{key}copy_src_' . uniqid();
1045+
$dstKey = '{key}copy_dst_' . uniqid();
1046+
1047+
$this->valkey_glide->select(0);
1048+
$this->valkey_glide->set($srcKey, 'copy_test_value');
1049+
1050+
// COPY with DB parameter should work in Valkey 9.0+ clusters
1051+
$result = $this->valkey_glide->copy($srcKey, $dstKey, ['DB' => 1]);
1052+
$this->assertTrue($result, 'COPY should succeed in Valkey 9.0+ cluster');
1053+
1054+
// Verify COPY worked correctly
1055+
$this->assertEquals('copy_test_value', $this->valkey_glide->get($srcKey)); // Original still exists
1056+
1057+
$this->valkey_glide->select(1);
1058+
$this->assertEquals('copy_test_value', $this->valkey_glide->get($dstKey)); // Copy exists
1059+
1060+
// Clean up
1061+
$this->valkey_glide->del($dstKey);
1062+
$this->valkey_glide->select(0);
1063+
$this->valkey_glide->del($srcKey);
1064+
}
9081065
}

tests/ValkeyGlideTest.php

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,23 +1694,23 @@ public function testMove()
16941694
$value2 = 'test_value2';
16951695

16961696
// Ensure we're in database 0
1697-
// $this->valkey_glide->select(0);
1697+
$this->valkey_glide->select(0);
16981698

16991699
// Clean up any existing keys
17001700
$this->valkey_glide->del($key1, $key2);
1701-
// $this->valkey_glide->select(1);
1701+
$this->valkey_glide->select(1);
17021702
$this->valkey_glide->del($key1, $key2);
1703-
// $this->valkey_glide->select(0);
1703+
$this->valkey_glide->select(0);
17041704

17051705
// Test successful move
17061706
$this->valkey_glide->set($key1, $value1);
17071707
$this->assertTrue($this->valkey_glide->move($key1, 1));
17081708

17091709
// Verify key moved
17101710
$this->assertEquals(0, $this->valkey_glide->exists($key1)); // Gone from db 0
1711-
// Verification disabled until select() command is supported.
1712-
// $this->valkey_glide->select(1);
1713-
// $this->assertKeyEquals($value1, $key1); // Present in db 1
1711+
// Verify key is present in database 1
1712+
$this->valkey_glide->select(1);
1713+
$this->assertKeyEquals($value1, $key1); // Present in db 1
17141714
}
17151715

17161716
public function testBlmove()
@@ -2655,13 +2655,11 @@ public function testInfoCommandStats()
26552655
}
26562656
}
26572657

2658-
/* public function testSelect()
2658+
public function testSelect()
26592659
{
26602660
$this->assertFalse(@$this->valkey_glide->select(-1));
26612661
$this->assertTrue($this->valkey_glide->select(0));
26622662
}
2663-
*/
2664-
26652663

26662664
public function testMset()
26672665
{
@@ -7501,6 +7499,60 @@ public function testCopy()
75017499
$this->assertKeyEquals('bar', '{key}dst');
75027500
}
75037501

7502+
public function testCopyWithDatabase()
7503+
{
7504+
if (version_compare($this->version, '6.2.0') < 0) {
7505+
$this->markTestSkipped();
7506+
}
7507+
7508+
// Test copy to different database using DB option
7509+
$this->valkey_glide->select(0);
7510+
$this->valkey_glide->del('{key}src', '{key}dst');
7511+
$this->valkey_glide->select(1);
7512+
$this->valkey_glide->del('{key}src', '{key}dst');
7513+
$this->valkey_glide->select(0);
7514+
7515+
$this->valkey_glide->set('{key}src', 'test_value');
7516+
7517+
// Test with string key - may fail if multi-database not supported
7518+
$result1 = $this->valkey_glide->copy('{key}src', '{key}dst', ['DB' => 1]);
7519+
$this->assertIsBool($result1);
7520+
7521+
if ($result1) {
7522+
// Verify key exists in database 1
7523+
$this->valkey_glide->select(1);
7524+
$this->assertKeyEquals('test_value', '{key}dst');
7525+
$this->valkey_glide->select(0);
7526+
}
7527+
7528+
// Test with constant
7529+
$this->valkey_glide->set('{key}src2', 'constant_test');
7530+
$result2 = $this->valkey_glide->copy('{key}src2', '{key}dst2', [ValkeyGlide::COPY_DB => 1]);
7531+
$this->assertIsBool($result2);
7532+
7533+
if ($result2) {
7534+
// Verify with constant
7535+
$this->valkey_glide->select(1);
7536+
$this->assertKeyEquals('constant_test', '{key}dst2');
7537+
$this->valkey_glide->select(0);
7538+
}
7539+
7540+
// Test combined options
7541+
if ($result1) {
7542+
$result3 = $this->valkey_glide->copy('{key}src', '{key}dst', [
7543+
ValkeyGlide::COPY_DB => 1,
7544+
ValkeyGlide::COPY_REPLACE => true
7545+
]);
7546+
$this->assertIsBool($result3);
7547+
}
7548+
7549+
// Clean up
7550+
$this->valkey_glide->del('{key}src', '{key}src2');
7551+
$this->valkey_glide->select(1);
7552+
$this->valkey_glide->del('{key}dst', '{key}dst2');
7553+
$this->valkey_glide->select(0);
7554+
}
7555+
75047556
public function testFunction()
75057557
{
75067558
if (version_compare($this->version, '7.0') < 0) {

0 commit comments

Comments
 (0)