From 4b112f8dc3cfe3ed7f47dcd1df12f4c35b171f37 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 18 Sep 2025 13:01:41 +0300 Subject: [PATCH 01/46] Fixed PHP lint errors. Signed-off-by: asafpamzn --- tests/ValkeyGlideBaseTest.php | 4 +- tests/ValkeyGlideBatchTest.php | 206 +++++++++++++------------- tests/ValkeyGlideClusterBaseTest.php | 4 +- tests/ValkeyGlideClusterBatchTest.php | 53 ++++--- tests/ValkeyGlideClusterTest.php | 10 +- tests/ValkeyGlideTest.php | 50 +++---- 6 files changed, 162 insertions(+), 165 deletions(-) diff --git a/tests/ValkeyGlideBaseTest.php b/tests/ValkeyGlideBaseTest.php index 7228f4f9..c2b06193 100644 --- a/tests/ValkeyGlideBaseTest.php +++ b/tests/ValkeyGlideBaseTest.php @@ -216,12 +216,12 @@ public function reset() /* Helper function to determine if the class has pipeline support */ protected function havePipeline() - { + { return defined(get_class($this->valkey_glide) . '::PIPELINE'); } protected function haveMulti() - { + { return defined(get_class($this->valkey_glide) . '::MULTI'); } } diff --git a/tests/ValkeyGlideBatchTest.php b/tests/ValkeyGlideBatchTest.php index a0b6ae45..14bfe7c5 100644 --- a/tests/ValkeyGlideBatchTest.php +++ b/tests/ValkeyGlideBatchTest.php @@ -15,7 +15,7 @@ public function __construct($host, $port, $auth, $tls) { parent::__construct($host, $port, $auth, $tls); } - + // =================================================================== // CORE STRING OPERATIONS BATCH TESTS // =================================================================== @@ -290,7 +290,7 @@ public function testHashIncrementOperationsBatch() $this->valkey_glide->del($key1); } - + // =================================================================== // SERVER & CONFIG OPERATIONS BATCH TESTS @@ -320,11 +320,11 @@ public function testServerOperationsBatch() public function testInfoOperationsBatch() { - + // Execute INFO, CLIENT ID, CLIENT GETNAME in multi/exec batch $results = $this->valkey_glide->multi() ->info() - ->client('id') + ->client('id') //->client('setname', 'phpredis_unit_tests') //TODO return once setname is supported ->client('getname') ->client('list') @@ -449,7 +449,7 @@ public function testKeyRenameBatch() public function testAdvancedKeyOperationsBatch() { - + $key1 = 'batch_adv_1_' . uniqid(); $key2 = 'batch_adv_2_' . uniqid(); $key3 = 'batch_adv_3_' . uniqid(); @@ -557,7 +557,7 @@ public function testBitAdvancedOperationsBatch() ->bitop('AND', $key3, $key1, $key2) ->bitcount($key3) ->exec(); - + // Verify transaction results $this->assertIsArray($results); $this->assertCount(3, $results); @@ -594,7 +594,7 @@ public function testHashAdvancedOperationsBatch() // Verify transaction results $this->assertIsArray($results); $this->assertCount(3, $results); - $this->assertEquals(['field1' =>'value1', 'field2' =>'value2', 'field_nonexistent' =>false], $results[0]); // HMGET result + $this->assertEquals(['field1' => 'value1', 'field2' => 'value2', 'field_nonexistent' => false], $results[0]); // HMGET result $this->assertCount(3, $results[1]); // HKEYS result $this->assertContains('field1', $results[1]); $this->assertCount(3, $results[2]); // HVALS result @@ -687,25 +687,25 @@ public function testHashRandfieldBatch() // Verify transaction results $this->assertIsArray($results); $this->assertCount(3, $results); - + // Single field result - should be a string (field name) $this->assertIsString($results[0]); // HRANDFIELD single result $this->assertContains($results[0], ['alpha', 'beta', 'gamma', 'delta']); - + // Multiple fields result - should be array of field names $this->assertIsArray($results[1]); // HRANDFIELD multiple result $this->assertCount(2, $results[1]); foreach ($results[1] as $field) { $this->assertContains($field, ['alpha', 'beta', 'gamma', 'delta']); } - + // Fields with values result - should be associative array $this->assertIsArray($results[2]); // HRANDFIELD with values result $this->assertCount(2, $results[2]); foreach ($results[2] as $field => $value) { $this->assertContains($field, ['alpha', 'beta', 'gamma', 'delta']); - $expectedValue = $field === 'alpha' ? 'value_a' : - ($field === 'beta' ? 'value_b' : + $expectedValue = $field === 'alpha' ? 'value_a' : + ($field === 'beta' ? 'value_b' : ($field === 'gamma' ? 'value_c' : 'value_d')); $this->assertEquals($expectedValue, $value); } @@ -726,7 +726,7 @@ public function testHashRandfieldBatch() // =================================================================== public function testListAdvancedOperationsBatch() - { + { $key1 = 'batch_list_adv_' . uniqid(); $key2 = 'batch_list_adv_2_' . uniqid(); @@ -736,7 +736,7 @@ public function testListAdvancedOperationsBatch() // Execute LINDEX, LREM, LINSERT in multi/exec batch $results = $this->valkey_glide->multi() ->lindex($key1, 2) - ->lrem($key1,'b', 1) // Remove first occurrence of 'b' + ->lrem($key1, 'b', 1) // Remove first occurrence of 'b' ->linsert($key1, 'BEFORE', 'c', 'inserted') ->exec(); @@ -758,7 +758,7 @@ public function testListAdvancedOperationsBatch() public function testListPositionBatch() { - + $key1 = 'batch_list_pos_' . uniqid(); // Setup initial list @@ -788,7 +788,7 @@ public function testListPositionBatch() public function testListMoveBatch() { - + $key1 = '{momtom}batch_list_move_src_' . uniqid(); $key2 = '{momtom}batch_list_move_dst_' . uniqid(); @@ -825,7 +825,7 @@ public function testListMoveBatch() // =================================================================== public function testSetRandomOperationsBatch() - { + { $key1 = 'batch_set_rand_' . uniqid(); // Setup initial set @@ -837,10 +837,10 @@ public function testSetRandomOperationsBatch() ->srandmember($key1, 2) // Get 2 random members ->scard($key1) ->exec(); - + // Verify transaction results $this->assertIsArray($results); - + $this->assertCount(3, $results); $this->assertNotNull($results[0]); // SPOP result (random removed member) $this->assertIsArray($results[1]); // SRANDMEMBER result @@ -857,7 +857,7 @@ public function testSetRandomOperationsBatch() public function testSetOperationsBatch() { - + $key1 = '{ppp}batch_set_ops_1_' . uniqid(); $key2 = '{ppp}batch_set_ops_2_' . uniqid(); $key3 = '{ppp}batch_set_ops_3_' . uniqid(); @@ -892,8 +892,8 @@ public function testSetOperationsBatch() } public function testSetStoreBatch() - { - + { + $key1 = '{prefix}batch_set_store_1_' . uniqid(); $key2 = '{prefix}batch_set_store_2_' . uniqid(); $key3 = '{prefix}batch_set_store_3_' . uniqid(); @@ -962,7 +962,7 @@ public function testSortedSetRankBatch() public function testSortedSetPopBatch() { - + $key1 = 'batch_zset_pop_' . uniqid(); // Setup initial sorted set @@ -977,8 +977,8 @@ public function testSortedSetPopBatch() // Verify transaction results $this->assertIsArray($results); - $this->assertCount(3, $results); - $this->assertEquals(['a' => 1.0], $results[0]); // ZPOPMIN result + $this->assertCount(3, $results); + $this->assertEquals(['a' => 1.0], $results[0]); // ZPOPMIN result $this->assertEquals(['e' => 5.0], $results[1]); // ZPOPMAX result $this->assertEquals(3, $results[2]); // ZCARD result (after pops) @@ -1027,7 +1027,7 @@ public function testSortedSetRemoveRankBatch() public function testExpirationTimeBatch() { - + $key1 = '{fox}batch_exptime_1_' . uniqid(); $key2 = '{fox}batch_exptime_2_' . uniqid(); $key3 = '{fox}batch_exptime_3_' . uniqid(); @@ -1145,7 +1145,7 @@ public function testWaitBatch() public function testGeospatialOperationsBatch() { - + $key1 = 'batch_geo_' . uniqid(); // Execute GEOADD, GEOPOS, GEODIST in multi/exec batch @@ -1174,7 +1174,7 @@ public function testGeospatialOperationsBatch() public function testGeospatialAdvancedBatch() { - + $key1 = '{geotest}batch_geo_adv_' . uniqid(); // Setup initial geo data @@ -1202,9 +1202,9 @@ public function testGeospatialAdvancedBatch() // Verify transaction results $this->assertIsArray($results); $this->assertCount(3, $results); - $this->assertIsArray($results[0]); // GEOHASH result + $this->assertIsArray($results[0]); // GEOHASH result $this->assertEquals(["Golden Gate Bridge", "Crissy Field", "Lombard Street"], $results[1]); // GEOSEARCH result - $this->assertEquals(3, $results[2]); // GEOSEARCHSTORE result + $this->assertEquals(3, $results[2]); // GEOSEARCHSTORE result // Verify server-side effects $this->assertEquals(1, $this->valkey_glide->exists($storeKey)); // Store key created @@ -1217,7 +1217,7 @@ public function testGeospatialAdvancedBatch() // =================================================================== public function testScanOperationsBatch() - { + { $key1 = '{scantest}batch_scan_set_' . uniqid(); $key2 = '{scantest}batch_scan_hash_' . uniqid(); $key3 = '{scantest}batch_scan_zset_' . uniqid(); @@ -1238,19 +1238,19 @@ public function testScanOperationsBatch() ->sscan($key1, $sscan_it) ->hscan($key2, $hscan_it) ->exec(); - + // Verify transaction results $this->assertIsArray($results); $this->assertCount(3, $results); - $this->assertIsArray($results[0]); // SCAN result [cursor, keys] - $temp_it = null; + $this->assertIsArray($results[0]); // SCAN result [cursor, keys] + $temp_it = null; $this->assertEquals($this->valkey_glide->scan($temp_it), $results[0]); $this->assertIsArray($results[1]); // SSCAN result [cursor, members] $sscan_it = null; - $this->assertEquals($results[1],$this->valkey_glide->sscan($key1, $sscan_it)); - $this->assertIsArray($results[2]); // HSCAN result [cursor, fields_values] + $this->assertEquals($results[1], $this->valkey_glide->sscan($key1, $sscan_it)); + $this->assertIsArray($results[2]); // HSCAN result [cursor, fields_values] $hscan_it = null; - $this->assertEquals($results[2],$this->valkey_glide->hscan($key2, $hscan_it)); + $this->assertEquals($results[2], $this->valkey_glide->hscan($key2, $hscan_it)); // Verify server-side effects (scan operations don't modify data) $this->assertEquals(3, $this->valkey_glide->scard($key1)); $this->assertEquals(2, $this->valkey_glide->hlen($key2)); @@ -1261,7 +1261,7 @@ public function testScanOperationsBatch() public function testZscanBatch() { - + $key1 = 'batch_zscan_' . uniqid(); // Setup test data @@ -1307,16 +1307,16 @@ public function testSortOperationsBatch() $results = $this->valkey_glide->multi() ->sort($key1) ->sort_ro($key1, ['sort' => 'DESC']) - ->sort($key1, ['sort' => 'ASC', 'STORE'=> $key2]) + ->sort($key1, ['sort' => 'ASC', 'STORE' => $key2]) ->exec(); // Verify transaction results $this->assertIsArray($results); $this->assertCount(3, $results); $this->assertEquals(['1', '2', '3', '4', '5'], $results[0]); // SORT result - $this->assertEquals(['5', '4', '3', '2', '1'], $results[1]); // SORT_RO result + $this->assertEquals(['5', '4', '3', '2', '1'], $results[1]); // SORT_RO result $this->assertEquals(5, $results[2]); // SORT with STORE result (count of stored elements) - + // Verify server-side effects $this->assertEquals(5, $this->valkey_glide->llen($key2)); // Sorted list stored $storedList = $this->valkey_glide->lrange($key2, 0, -1); @@ -1332,7 +1332,7 @@ public function testSortOperationsBatch() public function testCopyDumpRestoreBatch() { - + $key1 = '{test}batch_copy_src_' . uniqid(); $key2 = '{test}batch_copy_dst_' . uniqid(); $key3 = '{test}batch_restore_' . uniqid(); @@ -1344,7 +1344,7 @@ public function testCopyDumpRestoreBatch() $results = $this->valkey_glide->multi() ->copy($key1, $key2) ->dump($key1) - ->restore($key3, 0, $res_key1) + ->restore($key3, 0, $res_key1) ->exec(); // Verify transaction results @@ -1353,7 +1353,7 @@ public function testCopyDumpRestoreBatch() $this->assertTrue($results[0]); // COPY result (success) $this->assertIsString($results[1]); // DUMP result (serialized data) // RESTORE result depends on having valid dump data - + // Verify server-side effects $this->assertEquals('test_value', $this->valkey_glide->get($key2)); // Copied value @@ -1369,7 +1369,7 @@ public function testCopyDumpRestoreBatch() // =================================================================== public function testMoveBatch() - { + { //TODO return once select is supported $this->markTestSkipped(); $key1 = 'batch_move_' . uniqid(); @@ -1409,7 +1409,7 @@ public function testMoveBatch() public function testGetDelExBatch() { - + $key1 = '{GD}batch_getdel_' . uniqid(); $key2 = '{GD}batch_getex_' . uniqid(); $key3 = '{GD}batch_getex2_' . uniqid(); @@ -1448,7 +1448,7 @@ public function testGetDelExBatch() public function testObjectOperationsBatch() { - + $key1 = 'batch_object_' . uniqid(); // Setup test data @@ -1480,7 +1480,7 @@ public function testObjectOperationsBatch() public function testSetMembershipBatch() { - + $key1 = '{bb}batch_smember_' . uniqid(); $key2 = '{bb}batch_smove_src_' . uniqid(); $key3 = '{bb}batch_smove_dst_' . uniqid(); @@ -1680,8 +1680,8 @@ protected function sequence($mode) $this->assertEquals('lvalue', $ret[$i++]); // rpoplpush returns the element: 'lvalue' $this->assertEquals(['lvalue'], $ret[$i++]); // rpoplpush returns the element: 'lvalue' $this->assertEquals('lvalue', $ret[$i++]); // pop returns the front element: 'lvalue' - $this->assertEquals($i, count($ret)); - + $this->assertEquals($i, count($ret)); + $ret = $this->valkey_glide->multi($mode) ->del('{key}1') ->set('{key}1', 'value1') @@ -1725,7 +1725,7 @@ protected function sequence($mode) $this->assertEquals(4, $ret[$i++]); // decrby('{key}2', 5) $this->assertEqualsWeak(4, $ret[$i++]); // get('{key}2') $this->assertTrue($ret[$i++]); - + $ret = $this->valkey_glide->multi($mode) ->del('{key}1') @@ -1812,7 +1812,7 @@ protected function sequence($mode) $this->assertFalse($ret[$i++]); // can't set list[1] if we only have a single value in it. $this->assertEquals(['lvalue'], $ret[$i++]); // the previous error didn't touch anything. $this->assertEquals(1, $ret[$i++]); // the previous error didn't change the length - $this->assertEquals($i, count($ret)); + $this->assertEquals($i, count($ret)); // sets $ret = $this->valkey_glide->multi($mode) @@ -1908,7 +1908,7 @@ protected function sequence($mode) ->zadd('{z}key1', 15, 'zValue15') ->zRemRangeByScore('{z}key1', 11, 13) ->zrange('{z}key1', 0, -1) - ->zRange('{z}key1', 0, -1,['rev']) + ->zRange('{z}key1', 0, -1, ['rev']) ->zRangeByScore('{z}key1', 1, 6) ->zCard('{z}key1') ->zScore('{z}key1', 'zValue15') @@ -1944,7 +1944,7 @@ protected function sequence($mode) $this->assertEquals(['zValue1', 'zValue5', 'zValue14', 'zValue15'], $ret[$i++]); $this->assertEquals(['zValue15', 'zValue14', 'zValue5', 'zValue1'], $ret[$i++]);//zRangeByScore $this->assertEquals(['zValue1', 'zValue5'], $ret[$i++]);//zcard - $this->assertEquals(4, $ret[$i++]); // 4 elements + $this->assertEquals(4, $ret[$i++]); // 4 elements $this->assertEquals(15.0, $ret[$i++]); $this->assertEquals(1, $ret[$i++]); // added value $this->assertEquals(1, $ret[$i++]); // added value @@ -1959,7 +1959,7 @@ protected function sequence($mode) $this->assertEquals(8.0, $ret[$i++]); // current score is 8. $this->assertFalse($ret[$i++]); // score for unknown element. - $this->assertEquals($i, count($ret)); + $this->assertEquals($i, count($ret)); // hash $ret = $this->valkey_glide->multi($mode) @@ -2102,12 +2102,12 @@ protected function differentType($mode) $this->assertIsArray($ret); $this->assertTrue(is_long($ret[$i++])); // delete $this->assertTrue($ret[$i++]); // set - + $this->assertFalse($ret[$i++]); // rpush $this->assertFalse($ret[$i++]); // lpush $this->assertFalse($ret[$i++]); // llen $this->assertFalse($ret[$i++]); // lpop - $this->assertFalse($ret[$i++]); // lrange + $this->assertFalse($ret[$i++]); // lrange $this->assertFalse($ret[$i++]); // ltrim $this->assertFalse($ret[$i++]); // lindex $this->assertFalse($ret[$i++]); // lset @@ -2624,14 +2624,14 @@ protected function differentType($mode) } public function testDifferentType() - { - $this->differentType(ValkeyGlide::MULTI); + { + $this->differentType(ValkeyGlide::MULTI); $this->differentType(ValkeyGlide::PIPELINE); } public function testMultiExec() - { - $this->sequence(ValkeyGlide::MULTI); - + { + $this->sequence(ValkeyGlide::MULTI); + $this->valkey_glide->set('x', '42'); $this->assertTrue($this->valkey_glide->watch('x')); @@ -2646,14 +2646,14 @@ public function testFailedTransactions() $this->valkey_glide->set('x', 42); // failed transaction - $this->valkey_glide->watch('x'); - + $this->valkey_glide->watch('x'); + $r = $this->newInstance(); // new instance, modifying `x'. $r->incr('x'); $ret = $this->valkey_glide->multi()->get('x')->exec(); $this->assertFalse($ret); // failed because another client changed our watched key between WATCH and EXEC. - + // watch and unwatch $this->valkey_glide->watch('x'); $r->incr('x'); // other instance @@ -2836,7 +2836,7 @@ public function testMultiString() public function testListPushOperationsBatch() { - + $key1 = 'batch_list_1_' . uniqid(); $key2 = 'batch_list_2_' . uniqid(); $key3 = 'batch_list_3_' . uniqid(); @@ -2866,7 +2866,7 @@ public function testListPushOperationsBatch() public function testListPopOperationsBatch() { - + $key1 = 'batch_list_pop_' . uniqid(); // Setup initial list @@ -2884,7 +2884,7 @@ public function testListPopOperationsBatch() $this->assertCount(3, $results); $this->assertEquals('item1', $results[0]); // LPOP result $this->assertEquals('item4', $results[1]); // RPOP result - $this->assertEquals(2, $results[2]); + $this->assertEquals(2, $results[2]); // Verify server-side effects $this->assertEquals(2, $this->valkey_glide->llen($key1)); // 2 items remaining $listContents = $this->valkey_glide->lrange($key1, 0, -1); @@ -2896,7 +2896,7 @@ public function testListPopOperationsBatch() public function testListRangeOperationsBatch() { - + $key1 = 'batch_list_range_' . uniqid(); // Setup initial list @@ -2914,7 +2914,7 @@ public function testListRangeOperationsBatch() $this->assertCount(3, $results); $this->assertEquals(['a', 'b', 'c'], $results[0]); // LRANGE result (first 3) $this->assertTrue($results[1]); // LTRIM result - $this->assertEquals(['b', 'c', 'd', 'e'], $results[2]); + $this->assertEquals(['b', 'c', 'd', 'e'], $results[2]); // Verify server-side effects after transaction $finalContents = $this->valkey_glide->lrange($key1, 0, -1); @@ -2930,7 +2930,7 @@ public function testListRangeOperationsBatch() public function testSetAddOperationsBatch() { - + $key1 = 'batch_set_1_' . uniqid(); $key2 = 'batch_set_2_' . uniqid(); @@ -2961,7 +2961,7 @@ public function testSetAddOperationsBatch() public function testSetRemoveOperationsBatch() { - + $key1 = 'batch_set_rem_' . uniqid(); // Setup initial set @@ -2996,7 +2996,7 @@ public function testSetRemoveOperationsBatch() public function testSortedSetAddOperationsBatch() { - + $key1 = 'batch_zset_1_' . uniqid(); // Execute ZADD, ZCARD, ZRANGE in multi/exec batch @@ -3023,7 +3023,7 @@ public function testSortedSetAddOperationsBatch() public function testSortedSetScoreOperationsBatch() { - + $key1 = 'batch_zset_score_' . uniqid(); // Setup initial sorted set @@ -3047,12 +3047,12 @@ public function testSortedSetScoreOperationsBatch() $this->assertEquals(25.0, $this->valkey_glide->zscore($key1, 'member1')); // Score updated $this->assertEquals(1, $this->valkey_glide->zrank($key1, 'member1')); // Rank updated - // Cleanup + // Cleanup $this->valkey_glide->del($key1); } public function testSortedSetRemoveOperationsBatch() - { + { $key1 = 'batch_zset_rem_' . uniqid(); // Setup initial sorted set @@ -3061,15 +3061,15 @@ public function testSortedSetRemoveOperationsBatch() // Execute ZREM, ZREMRANGEBYSCORE, ZCARD in multi/exec batch $results = $this->valkey_glide->multi() ->zcard($key1) - ->zrem($key1, 'c') - ->zremrangebyscore($key1, 4, 5) + ->zrem($key1, 'c') + ->zremrangebyscore($key1, 4, 5) ->exec(); // Verify transaction results $this->assertIsArray($results); $this->assertCount(3, $results); $this->assertEquals(5, $results[0]); // ZCARD result (before removals in transaction) - $this->assertEquals(1, $results[1]); // ZREM result (1 member removed) + $this->assertEquals(1, $results[1]); // ZREM result (1 member removed) $this->assertEquals(2, $results[2]); // ZREMRANGEBYSCORE result (2 members removed) // Verify server-side effects @@ -3120,7 +3120,7 @@ public function testBlockingListOperationsBatch() public function testAdvancedListMoveOperationsBatch() { - + $key1 = '{test_list}batch_list_move_1_' . uniqid(); $key2 = '{test_list}batch_list_move_2_' . uniqid(); $key3 = '{test_list}batch_list_move_3_' . uniqid(); @@ -3139,8 +3139,8 @@ public function testAdvancedListMoveOperationsBatch() // Verify transaction results $this->assertIsArray($results); $this->assertCount(3, $results); - $this->assertEquals('a', $results[0]); // BLMOVE result (moved element) - $this->assertEquals([$key1, ['c']], $results[1]); // LMPOP result + $this->assertEquals('a', $results[0]); // BLMOVE result (moved element) + $this->assertEquals([$key1, ['c']], $results[1]); // LMPOP result $this->assertEquals(1, $results[2]); // LLEN result (after moves) // Verify server-side effects @@ -3185,7 +3185,7 @@ public function testConditionalListPushBatch() public function testBlockingListMultiPopBatch() { - + $key1 = '{test_list}batch_blmpop_1_' . uniqid(); $key2 = '{test_list}batch_blmpop_2_' . uniqid(); $key3 = '{test_list}batch_blmpop_3_' . uniqid(); @@ -3221,7 +3221,7 @@ public function testBlockingListMultiPopBatch() // =================================================================== public function testStreamBasicOperationsBatch() - { + { $stream1 = '{test_stream}batch_stream_1_' . uniqid(); $stream2 = '{test_stream}batch_stream_2_' . uniqid(); @@ -3314,7 +3314,7 @@ public function testStreamGroupOperationsBatch() public function testStreamGroupManagementBatch() { - + $stream1 = 'batch_stream_mgmt_' . uniqid(); $group1 = 'batch_group_mgmt'; @@ -3493,7 +3493,7 @@ public function testStreamTrimOperationsBatch() // =================================================================== public function testHyperLogLogOperationsBatch() - { + { $key1 = '{test_hll}batch_hll_1_' . uniqid(); $key2 = '{test_hll}batch_hll_2_' . uniqid(); $key3 = '{test_hll}batch_hll_3_' . uniqid(); @@ -3512,7 +3512,7 @@ public function testHyperLogLogOperationsBatch() $this->assertEquals(1, $results[1]); // PFADD result (HLL was altered) $this->assertGTE(4, $results[2]); // PFCOUNT result (approximate count >= 4) $this->assertLTE(6, $results[2]); // PFCOUNT result (approximate count <= 6) - + // Verify server-side effects $count1 = $this->valkey_glide->pfcount($key1); $count2 = $this->valkey_glide->pfcount($key2); @@ -3684,7 +3684,7 @@ public function testSortedSetRangeStoreBatch() public function testSortedSetInterUnionBatch() { - + $key1 = '{test_zset}batch_zset_inter_1_' . uniqid(); $key2 = '{test_zset}batch_zset_inter_2_' . uniqid(); $key3 = '{test_zset}batch_zset_inter_3_' . uniqid(); @@ -3704,7 +3704,7 @@ public function testSortedSetInterUnionBatch() $this->assertIsArray($results); $this->assertCount(3, $results); $this->assertEquals(['b', 'c'], $results[0]); // ZINTER result - $this->assertEquals(['a', 'b', 'd', 'c'], $results[1]); // ZUNION result + $this->assertEquals(['a', 'b', 'd', 'c'], $results[1]); // ZUNION result $this->assertEquals(2, $results[2]); // ZINTERCARD result // Verify server-side effects (original sets unchanged) @@ -3717,7 +3717,7 @@ public function testSortedSetInterUnionBatch() public function testSortedSetMultiPopBatch() { - + $key1 = '{test_zset}batch_zset_mpop_1_' . uniqid(); $key2 = '{test_zset}batch_zset_mpop_2_' . uniqid(); @@ -3754,7 +3754,7 @@ public function testSortedSetMultiPopBatch() public function testLongestCommonSubsequenceBatch() { - + $key1 = '{test_lcs}batch_lcs_1_' . uniqid(); $key2 = '{test_lcs}batch_lcs_2_' . uniqid(); $key3 = '{test_lcs}batch_lcs_3_' . uniqid(); @@ -3773,7 +3773,7 @@ public function testLongestCommonSubsequenceBatch() // Verify transaction results $this->assertIsArray($results); $this->assertCount(3, $results); - $this->assertIsString($results[0]); // LCS result (common subsequence) + $this->assertIsString($results[0]); // LCS result (common subsequence) $this->assertIsInt($results[1]); // LCS LEN result (length) $this->assertIsArray($results[2]); // LCS IDX result (with indexes) @@ -3791,27 +3791,27 @@ public function testLongestCommonSubsequenceBatch() public function testFunctionManagementBatch() { - + $functionCode = "#!lua name=mylib\nredis.register_function('myfunc', function(keys, args) return args[1] end)"; // Execute FUNCTION LOAD, FUNCTION LIST, FUNCTION DELETE in multi/exec batch $results = $this->valkey_glide->multi() ->function('LOAD', $functionCode) ->function('LIST') - ->fcall('myfunc', [], ['foo']) - ->function('load', 'replace',"#!lua name=mylib\nredis.register_function{function_name='myfunc_ro', callback=function(keys, args) return args[1] end, flags={'no-writes'}}") - ->fcall_ro('myfunc_ro', [], ['foo']) + ->fcall('myfunc', [], ['foo']) + ->function('load', 'replace', "#!lua name=mylib\nredis.register_function{function_name='myfunc_ro', callback=function(keys, args) return args[1] end, flags={'no-writes'}}") + ->fcall_ro('myfunc_ro', [], ['foo']) ->function('DELETE', 'mylib') ->exec(); - + // Verify transaction results $this->assertIsArray($results); $this->assertCount(6, $results); - $this->assertEquals('mylib', $results[0]); // FUNCTION LOAD result - $this->assertIsArray($results[1]); // FUNCTION LIST result - $this->assertEquals('foo',$results[2]); // fcall result - $this->assertEquals('mylib', $results[3]); // FUNCTION LOAD result - $this->assertEquals('foo',$results[4]); // fcall_ro result + $this->assertEquals('mylib', $results[0]); // FUNCTION LOAD result + $this->assertIsArray($results[1]); // FUNCTION LIST result + $this->assertEquals('foo', $results[2]); // fcall result + $this->assertEquals('mylib', $results[3]); // FUNCTION LOAD result + $this->assertEquals('foo', $results[4]); // fcall_ro result $this->assertTrue($results[5]); // FUNCTION DELETE result // Verify server-side effects @@ -3822,7 +3822,7 @@ public function testFunctionManagementBatch() public function testFunctionDumpRestoreBatch() { - + $functionCode = "#!lua name=mylib\nredis.register_function('myfunc', function(keys, args) return args[1] end)"; // Setup function diff --git a/tests/ValkeyGlideClusterBaseTest.php b/tests/ValkeyGlideClusterBaseTest.php index 0dc29bee..705862f7 100644 --- a/tests/ValkeyGlideClusterBaseTest.php +++ b/tests/ValkeyGlideClusterBaseTest.php @@ -93,8 +93,8 @@ abstract class ValkeyGlideClusterBaseTest extends ValkeyGlideBaseTest private static string $seed_source = ''; - - + + /* Load our seeds on construction */ public function __construct($host, $port, $auth, $tls) diff --git a/tests/ValkeyGlideClusterBatchTest.php b/tests/ValkeyGlideClusterBatchTest.php index 782cbc43..37dac880 100644 --- a/tests/ValkeyGlideClusterBatchTest.php +++ b/tests/ValkeyGlideClusterBatchTest.php @@ -48,15 +48,14 @@ protected function newInstance() public function testServerOperationsBatch() { - } public function testInfoOperationsBatch() { - + // Execute INFO, CLIENT ID, CLIENT GETNAME in multi/exec batch - $results = $this->valkey_glide->multi() - ->client('id') + $results = $this->valkey_glide->multi() + ->client('id') // ->client('setname', 'phpredis_unit_tests')//TODO return once setname is supported ->client('getname') ->client('list') @@ -64,11 +63,11 @@ public function testInfoOperationsBatch() // Verify transaction results $this->assertIsArray($results); - $this->assertCount(3, $results); + $this->assertCount(3, $results); $this->assertIsInt($results[0]); // CLIENT ID result (integer) // CLIENT GETNAME might return null if no name is set - $this->assertEquals('valkey-glide-php', $results[1]); // CLIENT SETNAME result - + $this->assertEquals('valkey-glide-php', $results[1]); // CLIENT SETNAME result + $this->assertGT(0, $results[0]); // Client ID should be positive } @@ -77,25 +76,25 @@ public function testDatabaseOperationsBatch() $key1 = '{xxx}batch_db_' . uniqid(); // Execute SELECT, DBSIZE, TYPE in multi/exec batch - $results = $this->valkey_glide->multi() + $results = $this->valkey_glide->multi() ->set('{xxx}x', 'y') - ->set($key1, 'test_value') + ->set($key1, 'test_value') ->type($key1) ->exec(); // Verify transaction results $this->assertIsArray($results); - $this->assertCount(3, $results); + $this->assertCount(3, $results); $this->assertEquals(ValkeyGlide::VALKEY_GLIDE_STRING, $results[2]); // TYPE result - + // Cleanup $this->valkey_glide->del($key1); } - public function testAdvancedKeyOperationsBatch() + public function testAdvancedKeyOperationsBatch() { - + $key1 = '{xyz}batch_adv_1_' . uniqid(); $key2 = '{xyz}batch_adv_2_' . uniqid(); $key3 = '{xyz}batch_adv_3_' . uniqid(); @@ -105,9 +104,9 @@ public function testAdvancedKeyOperationsBatch() // Execute UNLINK, TOUCH, RANDOMKEY in multi/exec batch $results = $this->valkey_glide->multi() - ->unlink($key1) - ->touch($key2, $key3) // Touch non-existing keys - ->exec(); + ->unlink($key1) + ->touch($key2, $key3) // Touch non-existing keys + ->exec(); // Verify transaction results $this->assertIsArray($results); @@ -139,33 +138,33 @@ public function testWaitBatch() public function testScanOperationsBatch() - { + { $key1 = '{scantest}batch_scan_set_' . uniqid(); - $key2 = '{scantest}batch_scan_hash_' . uniqid(); + $key2 = '{scantest}batch_scan_hash_' . uniqid(); // Setup test data $this->valkey_glide->del($key1, $key2); $this->valkey_glide->sadd($key1, 'member1', 'member2', 'member3'); - $this->valkey_glide->hset($key2, 'field1', 'value1', 'field2', 'value2'); + $this->valkey_glide->hset($key2, 'field1', 'value1', 'field2', 'value2'); - // Execute SSCAN, HSCAN in multi/exec batch + // Execute SSCAN, HSCAN in multi/exec batch $sscan_it = null; $hscan_it = null; - $results = $this->valkey_glide->multi() + $results = $this->valkey_glide->multi() ->sscan($key1, $sscan_it) ->hscan($key2, $hscan_it) ->exec(); - + // Verify transaction results $this->assertIsArray($results); - $this->assertCount(2, $results); + $this->assertCount(2, $results); $this->assertIsArray($results[0]); // SSCAN result [cursor, members] $sscan_it = null; - $this->assertEquals($results[0],$this->valkey_glide->sscan($key1, $sscan_it)); - $this->assertIsArray($results[1]); // HSCAN result [cursor, fields_values] + $this->assertEquals($results[0], $this->valkey_glide->sscan($key1, $sscan_it)); + $this->assertIsArray($results[1]); // HSCAN result [cursor, fields_values] $hscan_it = null; - $this->assertEquals($results[1],$this->valkey_glide->hscan($key2, $hscan_it)); + $this->assertEquals($results[1], $this->valkey_glide->hscan($key2, $hscan_it)); // Verify server-side effects (scan operations don't modify data) $this->assertEquals(3, $this->valkey_glide->scard($key1)); $this->assertEquals(2, $this->valkey_glide->hlen($key2)); @@ -181,7 +180,7 @@ public function testMoveBatch() public function testFunctionManagementBatch() { - + // FUNCTION management is not supported in batch and cluster mode } diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index 07e0684a..fcdceb50 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -91,7 +91,7 @@ class ValkeyGlideClusterTest extends ValkeyGlideTest - protected static array $seeds = []; + protected static array $seeds = []; private static string $seed_source = ''; /* Tests we'll skip all together in the context of ValkeyGlideCluster. The @@ -162,15 +162,15 @@ public function testFunction() } - - + + /* Load our seeds on construction */ public function __construct($host, $port, $auth, $tls) { - parent::__construct($host, $port, $auth, $tls); + parent::__construct($host, $port, $auth, $tls); } /* Override setUp to get info from a specific node */ @@ -364,7 +364,7 @@ public function testScan() /* Scan the keys here using ClusterScanCursor - create new cursor each iteration */ $cursor = new ClusterScanCursor(); // Create fresh cursor each time while (true) { - $keys = $this->valkey_glide->scan($cursor); + $keys = $this->valkey_glide->scan($cursor); if ($keys) { $scan_count += count($keys); } diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 424426d0..499f5b08 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -176,7 +176,7 @@ public function testBitcount() } } - + public function testBitop() { @@ -195,24 +195,24 @@ public function testBitop() for ($i = 1; $i <= 7; $i++) { $this->valkey_glide->set("{key}src{$i}", 'test' . $i); } - + $result7 = $this->valkey_glide->bitop('OR', '{key}dest7', '{key}src1', '{key}src2', '{key}src3', '{key}src4', '{key}src5', '{key}src6', '{key}src7'); - $this->assertEquals(5, $result7); - + $this->assertEquals(5, $result7); + // Test with 8 keys (should fail with current implementation due to 7-key limit) $this->valkey_glide->set('{key}src8', 'test8'); - + $result8 = $this->valkey_glide->bitop('OR', '{key}dest8', '{key}src1', '{key}src2', '{key}src3', '{key}src4', '{key}src5', '{key}src6', '{key}src7', '{key}src8'); $this->assertIsInt($result8); - $this->assertEquals(5, $result8); + $this->assertEquals(5, $result8); // Test with 10 keys (should definitely fail with current implementation) $this->valkey_glide->set('{key}src9', 'test9'); $this->valkey_glide->set('{key}src10', 'test10'); - + $result10 = $this->valkey_glide->bitop('AND', '{key}dest10', '{key}src1', '{key}src2', '{key}src3', '{key}src4', '{key}src5', '{key}src6', '{key}src7', '{key}src8', '{key}src9', '{key}src10'); - $this->assertEquals(6, $result10); + $this->assertEquals(6, $result10); // Clean up $keysToDelete = ['{key}1', '{key}2', '{key}dest1', '{key}dest7', '{key}dest8', '{key}dest10']; @@ -275,7 +275,7 @@ public function testLcs() $this->assertTrue($this->valkey_glide->set($key1, '12244447777777')); $this->assertTrue($this->valkey_glide->set($key2, '6666662244441')); - $this->assertEquals('224444', $this->valkey_glide->lcs($key1, $key2)); + $this->assertEquals('224444', $this->valkey_glide->lcs($key1, $key2)); $this->assertEquals( ['matches', [[[1, 6], [6, 11]]], 'len', 6], $this->valkey_glide->lcs($key1, $key2, ['idx']) @@ -726,15 +726,15 @@ public function testRenameNx() // lists $this->valkey_glide->del('{key}0'); $this->valkey_glide->del('{key}1'); - + $this->valkey_glide->lPush('{key}0', 'val0'); - + $this->valkey_glide->lPush('{key}0', 'val1'); $this->valkey_glide->lPush('{key}1', 'val1-0'); $this->valkey_glide->lPush('{key}1', 'val1-1'); - + $this->assertFalse($this->valkey_glide->renameNx('{key}0', '{key}1')); - $this->assertEquals(['val1', 'val0'], $this->valkey_glide->lRange('{key}0', 0, -1)); + $this->assertEquals(['val1', 'val0'], $this->valkey_glide->lRange('{key}0', 0, -1)); $this->assertEquals(['val1-1', 'val1-0'], $this->valkey_glide->lRange('{key}1', 0, -1)); $this->valkey_glide->del('{key}2'); @@ -1061,7 +1061,7 @@ public function testExists() $this->assertKeyMissing('key'); $this->valkey_glide->set('key', 'val'); $this->assertKeyExists('key'); - + /* Add multiple keys */ $mkeys = []; for ($i = 0; $i < 10; $i++) { @@ -1071,10 +1071,10 @@ public function testExists() $mkeys[] = $mkey; } } - + /* Test passing an array as well as the keys variadic */ - $this->assertEquals(count($mkeys), $this->valkey_glide->exists($mkeys)); - if (count($mkeys)) { + $this->assertEquals(count($mkeys), $this->valkey_glide->exists($mkeys)); + if (count($mkeys)) { $this->assertEquals(count($mkeys), $this->valkey_glide->exists(...$mkeys)); } } @@ -1304,7 +1304,6 @@ public function testblockingPop() // blocking blpop, brpop $this->valkey_glide->del('list'); - } public function testLLen() @@ -1366,7 +1365,6 @@ public function testlPos() $this->assertEquals([0, 1], $this->valkey_glide->lPos('key', 'val1', ['count' => 2])); $this->assertEquals([0], $this->valkey_glide->lPos('key', 'val1', ['count' => 2, 'maxlen' => 1])); $this->assertEquals([], $this->valkey_glide->lPos('key', 'val2', ['count' => 1])); - } // ltrim, lLen, lpop @@ -2539,7 +2537,7 @@ public function testInfo() public function testInfoCommandStats() { - + // INFO COMMANDSTATS is new in 2.6.0 if (version_compare($this->version, '2.5.0') < 0) { $this->markTestSkipped(); @@ -3467,7 +3465,7 @@ public function testBZPop() $this->valkey_glide->del('{zs}1', '{zs}2'); $this->valkey_glide->zAdd('{zs}1', 0, 'a', 1, 'b', 2, 'c'); $this->valkey_glide->zAdd('{zs}2', 3, 'A', 4, 'B', 5, 'D'); - + $this->assertEquals(['{zs}1', 'a', '0'], $this->valkey_glide->bzPopMin('{zs}1', '{zs}2', 0)); $this->assertEquals(['{zs}1', 'c', '2'], $this->valkey_glide->bzPopMax(['{zs}1', '{zs}2'], 0)); $this->assertEquals(['{zs}2', 'A', '3'], $this->valkey_glide->bzPopMin(['{zs}2', '{zs}1'], 0)); @@ -3729,8 +3727,8 @@ public function testHRandField() $this->assertEquals(2, count($result)); $xx = ['a' => 0, 'b' => 1, 'c' => 'foo', 'd' => 'bar', 'e' => null]; - $this->assertEquals(array_intersect_key($result, $xx), $result); - + $this->assertEquals(array_intersect_key($result, $xx), $result); + /* Make sure PhpValkeyGlide sends COUNt (1) when `WITHVALUES` is set */ $result = $this->valkey_glide->hRandField('key', ['withvalues' => true]); @@ -3823,7 +3821,7 @@ public function testObject() $this->assertTrue(is_numeric($this->valkey_glide->object('idletime', 'key'))); } - + /* GitHub issue #1211 (ignore redundant calls to pipeline or multi) */ public function testDoublePipeNoOp() @@ -3860,7 +3858,7 @@ public function testDiscard() /* Set and get in our transaction */ $this->valkey_glide->set('pipecount', 'over9000')->get('pipecount'); - + /* first call closes transaction and clears commands queue */ $this->assertTrue($this->valkey_glide->discard()); @@ -5351,7 +5349,7 @@ public function testXGroup() $this->assertFalse(@$this->valkey_glide->xGroup('CREATECONSUMER')); $this->assertFalse(@$this->valkey_glide->xGroup('create')); - + if (! $this->minVersionCheck('7.0.0')) { return; } From e1476f73f1009d1b972e53cd3544dbf3b9d710bd Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 18 Sep 2025 16:03:40 +0300 Subject: [PATCH 02/46] dead code removal --- command_response.c | 134 --------------- command_response.h | 29 ---- common.h | 2 + valkey_glide_commands_3.c | 7 +- valkey_glide_core_commands.c | 8 - valkey_glide_core_common.c | 7 - valkey_glide_hash_common.c | 23 --- valkey_glide_list_common.c | 8 - valkey_glide_s_common.c | 314 +++-------------------------------- valkey_glide_x_common.c | 48 ------ valkey_glide_z_common.c | 207 ++--------------------- 11 files changed, 44 insertions(+), 743 deletions(-) diff --git a/command_response.c b/command_response.c index 43f184c9..366638a1 100644 --- a/command_response.c +++ b/command_response.c @@ -389,40 +389,6 @@ CommandResult* execute_command(const void* glide_client, return result; } -/* Handle an integer response */ -long handle_int_response(CommandResult* result, long* output_value) { - /* Check if the command was successful */ - if (!result) { - return 0; /* False - failure */ - } - - /* Check if there was an error */ - if (result->command_error) { - printf("%s:%d - Error executing command: %s\n", - __FILE__, - __LINE__, - result->command_error->command_error_message); - free_command_result(result); - return 0; /* False - failure */ - } - - /* Get the result value */ - if (result->response && result->response->response_type == Int) { - *output_value = result->response->int_value; - - /* Free the result */ - free_command_result(result); - return 1; /* True - success */ - } else { - // printf("%s:%d - Unexpected response type for integer command\n", __FILE__, __LINE__); - assert(0); /* This should never happen */ - } - - /* Unexpected response type */ - free_command_result(result); - return 0; /* False - failure */ -} - /* Handle a string response */ int handle_string_response(CommandResult* result, char** output, size_t* output_len) { /* Check if the command was successful */ @@ -432,11 +398,6 @@ int handle_string_response(CommandResult* result, char** output, size_t* output_ /* Check if there was an error */ if (result->command_error) { - printf("%s:%d - Error executing command: %s\n", - __FILE__, - __LINE__, - result->command_error->command_error_message); - free_command_result(result); return -1; } @@ -492,63 +453,6 @@ int handle_string_response(CommandResult* result, char** output, size_t* output_ return ret_val; } -/* Handle a boolean response */ -int handle_bool_response(CommandResult* result) { - /* Check if the command was successful */ - if (!result) { - return -1; - } - - /* Check if there was an error */ - if (result->command_error) { - printf("%s:%d - Error executing command: %s\n", - __FILE__, - __LINE__, - result->command_error->command_error_message); - free_command_result(result); - return -1; - } - - /* Get the result value */ - int ret_val = -1; - if (result->response && result->response->response_type == Bool) { - ret_val = result->response->bool_value ? 1 : 0; - } - - /* Free the result */ - free_command_result(result); - - return ret_val; -} - -/* Handle an OK response */ -int handle_ok_response(CommandResult* result) { - /* Check if the command was successful */ - if (!result) { - return -1; - } - - /* Check if there was an error */ - if (result->command_error) { - printf("%s:%d - Error executing command: %s\n", - __FILE__, - __LINE__, - result->command_error->command_error_message); - free_command_result(result); - return -1; - } - - /* Get the result value */ - int ret_val = -1; - if (result->response && result->response->response_type == Ok) { - ret_val = 1; - } - - /* Free the result */ - free_command_result(result); - - return ret_val; -} /* Helper function to convert a CommandResponse to a PHP value * use_associative_array: @@ -794,44 +698,6 @@ int command_response_to_zval(CommandResponse* response, } } -/* Handle an array response */ -int handle_array_response(CommandResult* result, zval* output) { - /* Check if the command was successful */ - if (!result) { - return -1; - } - - /* Check if there was an error */ - if (result->command_error) { - printf("%s:%d - Error executing command: %s\n", - __FILE__, - __LINE__, - result->command_error->command_error_message); - free_command_result(result); - return -1; - } - - /* Process the result */ - int ret_val = -1; - if (result->response) { - if (result->response->response_type == Null) { - ZVAL_NULL(output); - ret_val = 0; - } else if (result->response->response_type == Array) { - ret_val = command_response_to_zval( - result->response, output, COMMAND_RESPONSE_NOT_ASSOSIATIVE, false); - } else { - ret_val = -1; - } - } - - /* Free the result */ - free_command_result(result); - - return ret_val; -} - - /* Handle a set response */ int handle_set_response(CommandResult* result, zval* output) { /* Check if the command was successful */ diff --git a/command_response.h b/command_response.h index fc1df9c8..7177ee42 100644 --- a/command_response.h +++ b/command_response.h @@ -51,13 +51,6 @@ CommandResult* execute_command_with_route(const void* glide_client, const unsigned long* args_len, zval* arg_route); -/* - * Handle an integer response - * Returns 0 on error, 1 on success - * The output_value parameter is set to the integer value on success - * This function frees the CommandResult - */ -long handle_int_response(CommandResult* result, long* output_value); /* * Handle a string response @@ -68,28 +61,6 @@ long handle_int_response(CommandResult* result, long* output_value); */ int handle_string_response(CommandResult* result, char** output, size_t* output_len); -/* - * Handle a boolean response - * Returns 1 for true, 0 for false, -1 on error - * This function frees the CommandResult - */ -int handle_bool_response(CommandResult* result); - -/* - * Handle an OK response - * Returns 1 on success, -1 on error - * This function frees the CommandResult - */ -int handle_ok_response(CommandResult* result); - -/* - * Handle an array response - * Returns 1 on success, 0 if null, -1 on error - * The output parameter is set to a PHP array - * This function frees the CommandResult - */ -int handle_array_response(CommandResult* result, zval* output); - /* * Handle a map response * Returns 1 on success, 0 if null, -1 on error diff --git a/common.h b/common.h index ac792747..d510c363 100644 --- a/common.h +++ b/common.h @@ -42,6 +42,8 @@ #define MULTI 0 #define PIPELINE 1 +#define VALKEY_GLIDE_MAX_OPTIONS 64 + /* ValkeyGlide Configuration Enums */ typedef enum { VALKEY_GLIDE_READ_FROM_PRIMARY = 0, diff --git a/valkey_glide_commands_3.c b/valkey_glide_commands_3.c index 84923cec..3fb26e61 100644 --- a/valkey_glide_commands_3.c +++ b/valkey_glide_commands_3.c @@ -107,9 +107,6 @@ static void clear_batch_state(valkey_glide_object* valkey_glide) { return; } - valkey_glide->is_in_batch_mode = false; - valkey_glide->batch_type = MULTI; - valkey_glide->command_count = 0; if (valkey_glide->buffered_commands) { /* Free each buffered command */ @@ -136,6 +133,10 @@ static void clear_batch_state(valkey_glide_object* valkey_glide) { valkey_glide->buffered_commands = NULL; valkey_glide->command_capacity = 0; } + + valkey_glide->is_in_batch_mode = false; + valkey_glide->batch_type = MULTI; + valkey_glide->command_count = 0; } /* Expand command buffer capacity */ diff --git a/valkey_glide_core_commands.c b/valkey_glide_core_commands.c index 59802c02..092bf8a1 100644 --- a/valkey_glide_core_commands.c +++ b/valkey_glide_core_commands.c @@ -1102,14 +1102,6 @@ static int process_info_sections( *cmd_args = (uintptr_t*) emalloc(count * sizeof(uintptr_t)); *cmd_args_len = (unsigned long*) emalloc(count * sizeof(unsigned long)); - if (!*cmd_args || !*cmd_args_len) { - if (*cmd_args) - efree(*cmd_args); - if (*cmd_args_len) - efree(*cmd_args_len); - return -1; - } - /* Process each section argument */ for (int i = 0; i < count; i++) { zval* section = &args[start_idx + i]; diff --git a/valkey_glide_core_common.c b/valkey_glide_core_common.c index 40648b25..c79e54ef 100644 --- a/valkey_glide_core_common.c +++ b/valkey_glide_core_common.c @@ -1149,13 +1149,6 @@ int allocate_core_arg_arrays(int count, uintptr_t** args_out, unsigned long** ar *args_out = (uintptr_t*) emalloc(count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(count * sizeof(unsigned long)); - if (!*args_out || !*args_len_out) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } return 1; } diff --git a/valkey_glide_hash_common.c b/valkey_glide_hash_common.c index 336bc3cb..466b0d36 100644 --- a/valkey_glide_hash_common.c +++ b/valkey_glide_hash_common.c @@ -235,14 +235,6 @@ int prepare_h_key_only_args(h_command_args_t* args, *allocated_strings = NULL; *allocated_count = 0; - if (!*args_out || !*args_len_out) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set key as the only argument */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -268,14 +260,6 @@ int prepare_h_single_field_args(h_command_args_t* args, *allocated_strings = NULL; *allocated_count = 0; - if (!*args_out || !*args_len_out) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set key and field arguments */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -303,13 +287,6 @@ int prepare_h_field_value_args(h_command_args_t* args, *allocated_strings = NULL; *allocated_count = 0; - if (!*args_out || !*args_len_out) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } /* Set key, field, and value arguments */ (*args_out)[0] = (uintptr_t) args->key; diff --git a/valkey_glide_list_common.c b/valkey_glide_list_common.c index 1333a36c..58eb94d7 100644 --- a/valkey_glide_list_common.c +++ b/valkey_glide_list_common.c @@ -31,14 +31,6 @@ int allocate_list_command_args(int count, uintptr_t** args_out, unsigned long** *args_out = (uintptr_t*) emalloc(count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(count * sizeof(unsigned long)); - if (!*args_out || !*args_len_out) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - return 1; } diff --git a/valkey_glide_s_common.c b/valkey_glide_s_common.c index ad3f012e..494d8396 100644 --- a/valkey_glide_s_common.c +++ b/valkey_glide_s_common.c @@ -35,14 +35,6 @@ int allocate_s_command_args(int count, uintptr_t** args_out, unsigned long** arg *args_out = (uintptr_t*) emalloc(count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(count * sizeof(unsigned long)); - if (!*args_out || !*args_len_out) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - return 1; } @@ -1427,45 +1419,13 @@ int execute_sinterstore_command(zval* object, int argc, zval* return_value, zend keys_count = keys_count - 1; } } - /* If we didn't get a single array, try other parameter formats */ - if (!has_destination) { + else { /* First argument is always the destination key */ if (zend_parse_method_parameters(1, object, "Os", &object, ce, &dst, &dst_len) == FAILURE) { return 0; } - /* Parse remaining args as variadic or array */ - if (argc == 2) { - /* Try to parse second parameter as an array */ - zval* second_arg; - if (zend_parse_parameters(1, "z", &second_arg) == SUCCESS && - Z_TYPE_P(second_arg) == IS_ARRAY) { - /* We have an array of source keys */ - ht_keys = Z_ARRVAL_P(second_arg); - keys_count = zend_hash_num_elements(ht_keys); - - /* If array is empty, return FALSE */ - if (keys_count == 0) { - return 0; - } - - /* Allocate memory for array of zvals */ - z_extracted_keys = ecalloc(keys_count, sizeof(zval)); - - /* Copy array values to sequential array */ - idx = 0; - ZEND_HASH_FOREACH_VAL(ht_keys, data) { - ZVAL_COPY(&z_extracted_keys[idx], data); - idx++; - } - ZEND_HASH_FOREACH_END(); - - /* Set for later use */ - z_args = z_extracted_keys; - } - } - /* If we didn't get an array as the second parameter, parse remaining args as variadic */ if (!z_args) { /* Parse all parameters including destination key */ @@ -1524,42 +1484,14 @@ int execute_sunion_command(zval* object, int argc, zval* return_value, zend_clas zval* z_extracted_keys = NULL; /* Check if we have a single array argument or variadic string arguments */ - if (argc == 1) { - /* Try to parse as a single array argument */ - if (zend_parse_method_parameters(argc, object, "Oa", &object, ce, &z_keys_arr) == SUCCESS) { - /* We have an array of keys */ - ht_keys = Z_ARRVAL_P(z_keys_arr); - keys_count = zend_hash_num_elements(ht_keys); - - /* If array is empty, return FALSE */ - if (keys_count == 0) { - return 0; - } - - /* Allocate memory for array of zvals */ - z_extracted_keys = ecalloc(keys_count, sizeof(zval)); - /* Copy array values to sequential array */ - zval* data; - int idx = 0; - ZEND_HASH_FOREACH_VAL(ht_keys, data) { - ZVAL_COPY(&z_extracted_keys[idx], data); - idx++; - } - ZEND_HASH_FOREACH_END(); + /* If we didn't get an array, parse as variadic arguments */ - /* Set for later use */ - z_args = z_extracted_keys; - } + if (zend_parse_method_parameters(argc, object, "O+", &object, ce, &z_args, &keys_count) == + FAILURE) { + return 0; } - /* If we didn't get an array, parse as variadic arguments */ - if (!z_args) { - if (zend_parse_method_parameters(argc, object, "O+", &object, ce, &z_args, &keys_count) == - FAILURE) { - return 0; - } - } /* Get ValkeyGlide object */ valkey_glide = VALKEY_GLIDE_PHP_ZVAL_GET_OBJECT(valkey_glide_object, object); @@ -1610,104 +1542,15 @@ int execute_sunionstore_command(zval* object, int argc, zval* return_value, zend int idx = 0; int has_destination = 0; - /* Check if we have a single array argument */ - if (argc == 1) { - /* Try to parse it as an array */ - if (zend_parse_method_parameters(argc, object, "Oa", &object, ce, &z_keys_arr) == SUCCESS) { - /* We have an array which will contain both destination and source keys */ - ht_keys = Z_ARRVAL_P(z_keys_arr); - keys_count = zend_hash_num_elements(ht_keys); - - /* We need at least one element (destination key) */ - if (keys_count == 0) { - return 0; - } - - /* Extract the first element as the destination key */ - HashPosition pointer; - zend_hash_internal_pointer_reset_ex(ht_keys, &pointer); - data = zend_hash_get_current_data_ex(ht_keys, &pointer); - if (data == NULL || Z_TYPE_P(data) != IS_STRING) { - return 0; - } - - /* Set the destination key */ - dst = Z_STRVAL_P(data); - dst_len = Z_STRLEN_P(data); - has_destination = 1; - - /* If there's only the destination key, return false */ - if (keys_count == 1) { - return 0; - } - - /* Move past the destination key */ - zend_hash_move_forward_ex(ht_keys, &pointer); - - /* Allocate memory for array of source keys (excluding destination) */ - z_extracted_keys = ecalloc(keys_count - 1, sizeof(zval)); - - /* Copy all remaining values (source keys) to sequential array */ - idx = 0; - while ((data = zend_hash_get_current_data_ex(ht_keys, &pointer))) { - ZVAL_COPY(&z_extracted_keys[idx], data); - idx++; - zend_hash_move_forward_ex(ht_keys, &pointer); - } - - /* Set for later use */ - z_args = z_extracted_keys; - keys_count = keys_count - 1; - } + /* First argument is always the destination key */ + if (zend_parse_method_parameters(1, object, "Os", &object, ce, &dst, &dst_len) == FAILURE) { + return 0; } - /* If we didn't get a single array, try other parameter formats */ - if (!has_destination) { - /* First argument is always the destination key */ - if (zend_parse_method_parameters(1, object, "Os", &object, ce, &dst, &dst_len) == FAILURE) { - return 0; - } - - /* Parse remaining args as variadic or array */ - if (argc == 2) { - /* Try to parse second parameter as an array */ - zval* second_arg; - if (zend_parse_parameters(1, "z", &second_arg) == SUCCESS && - Z_TYPE_P(second_arg) == IS_ARRAY) { - /* We have an array of source keys */ - ht_keys = Z_ARRVAL_P(second_arg); - keys_count = zend_hash_num_elements(ht_keys); - - /* If array is empty, return FALSE */ - if (keys_count == 0) { - return 0; - } - - /* Allocate memory for array of zvals */ - z_extracted_keys = ecalloc(keys_count, sizeof(zval)); - - /* Copy array values to sequential array */ - idx = 0; - ZEND_HASH_FOREACH_VAL(ht_keys, data) { - ZVAL_COPY(&z_extracted_keys[idx], data); - idx++; - } - ZEND_HASH_FOREACH_END(); - - /* Set for later use */ - z_args = z_extracted_keys; - } - } - - /* If we didn't get an array as the second parameter, parse remaining args as variadic */ - if (!z_args) { - /* Parse all parameters including destination key */ - if (zend_parse_method_parameters( - argc, object, "Os+", &object, ce, &dst, &dst_len, &z_args, &keys_count) == - FAILURE) { - return 0; - } - } + /* Parse all parameters including destination key */ + if (zend_parse_method_parameters( + argc, object, "Os+", &object, ce, &dst, &dst_len, &z_args, &keys_count) == FAILURE) { + return 0; } /* Get ValkeyGlide object */ @@ -1757,36 +1600,6 @@ int execute_sdiff_command(zval* object, int argc, zval* return_value, zend_class HashTable* ht_keys = NULL; zval* z_extracted_keys = NULL; - /* Check if we have a single array argument or variadic string arguments */ - if (argc == 1) { - /* Try to parse as a single array argument */ - if (zend_parse_method_parameters(argc, object, "Oa", &object, ce, &z_keys_arr) == SUCCESS) { - /* We have an array of keys */ - ht_keys = Z_ARRVAL_P(z_keys_arr); - keys_count = zend_hash_num_elements(ht_keys); - - /* If array is empty, return FALSE */ - if (keys_count == 0) { - return 0; - } - - /* Allocate memory for array of zvals */ - z_extracted_keys = ecalloc(keys_count, sizeof(zval)); - - /* Copy array values to sequential array */ - zval* data; - int idx = 0; - ZEND_HASH_FOREACH_VAL(ht_keys, data) { - ZVAL_COPY(&z_extracted_keys[idx], data); - idx++; - } - ZEND_HASH_FOREACH_END(); - - /* Set for later use */ - z_args = z_extracted_keys; - } - } - /* If we didn't get an array, parse as variadic arguments */ if (!z_args) { if (zend_parse_method_parameters(argc, object, "O+", &object, ce, &z_args, &keys_count) == @@ -1845,106 +1658,25 @@ int execute_sdiffstore_command(zval* object, int argc, zval* return_value, zend_ int idx = 0; int has_destination = 0; - /* Check if we have a single array argument */ - if (argc == 1) { - /* Try to parse it as an array */ - if (zend_parse_method_parameters(argc, object, "Oa", &object, ce, &z_keys_arr) == SUCCESS) { - /* We have an array which will contain both destination and source keys */ - ht_keys = Z_ARRVAL_P(z_keys_arr); - keys_count = zend_hash_num_elements(ht_keys); - - /* We need at least one element (destination key) */ - if (keys_count == 0) { - return 0; - } - - /* Extract the first element as the destination key */ - HashPosition pointer; - zend_hash_internal_pointer_reset_ex(ht_keys, &pointer); - data = zend_hash_get_current_data_ex(ht_keys, &pointer); - if (data == NULL || Z_TYPE_P(data) != IS_STRING) { - return 0; - } - - /* Set the destination key */ - dst = Z_STRVAL_P(data); - dst_len = Z_STRLEN_P(data); - has_destination = 1; - - /* If there's only the destination key, return false */ - if (keys_count == 1) { - return 0; - } - - /* Move past the destination key */ - zend_hash_move_forward_ex(ht_keys, &pointer); - /* Allocate memory for array of source keys (excluding destination) */ - z_extracted_keys = ecalloc(keys_count - 1, sizeof(zval)); - - /* Copy all remaining values (source keys) to sequential array */ - idx = 0; - while ((data = zend_hash_get_current_data_ex(ht_keys, &pointer))) { - ZVAL_COPY(&z_extracted_keys[idx], data); - idx++; - zend_hash_move_forward_ex(ht_keys, &pointer); - } + /* If we didn't get a single array, try other parameter formats */ - /* Set for later use */ - z_args = z_extracted_keys; - keys_count = keys_count - 1; - } + /* First argument is always the destination key */ + if (zend_parse_method_parameters(1, object, "Os", &object, ce, &dst, &dst_len) == FAILURE) { + return 0; } - /* If we didn't get a single array, try other parameter formats */ - if (!has_destination) { - /* First argument is always the destination key */ - if (zend_parse_method_parameters(1, object, "Os", &object, ce, &dst, &dst_len) == FAILURE) { + /* If we didn't get an array as the second parameter, parse remaining args as variadic */ + if (!z_args) { + /* Parse all parameters including destination key */ + if (zend_parse_method_parameters( + argc, object, "Os+", &object, ce, &dst, &dst_len, &z_args, &keys_count) == + FAILURE) { return 0; } - - /* Parse remaining args as variadic or array */ - if (argc == 2) { - /* Try to parse second parameter as an array */ - zval* second_arg; - if (zend_parse_parameters(1, "z", &second_arg) == SUCCESS && - Z_TYPE_P(second_arg) == IS_ARRAY) { - /* We have an array of source keys */ - ht_keys = Z_ARRVAL_P(second_arg); - keys_count = zend_hash_num_elements(ht_keys); - - /* If array is empty, return FALSE */ - if (keys_count == 0) { - return 0; - } - - /* Allocate memory for array of zvals */ - z_extracted_keys = ecalloc(keys_count, sizeof(zval)); - - /* Copy array values to sequential array */ - idx = 0; - ZEND_HASH_FOREACH_VAL(ht_keys, data) { - ZVAL_COPY(&z_extracted_keys[idx], data); - idx++; - } - ZEND_HASH_FOREACH_END(); - - /* Set for later use */ - z_args = z_extracted_keys; - } - } - - /* If we didn't get an array as the second parameter, parse remaining args as variadic */ - if (!z_args) { - /* Parse all parameters including destination key */ - if (zend_parse_method_parameters( - argc, object, "Os+", &object, ce, &dst, &dst_len, &z_args, &keys_count) == - FAILURE) { - return 0; - } - } } + /* Get ValkeyGlide object */ valkey_glide = VALKEY_GLIDE_PHP_ZVAL_GET_OBJECT(valkey_glide_object, object); diff --git a/valkey_glide_x_common.c b/valkey_glide_x_common.c index 6a20f409..58853d6b 100644 --- a/valkey_glide_x_common.c +++ b/valkey_glide_x_common.c @@ -336,13 +336,6 @@ int allocate_command_args(int count, uintptr_t** args_out, unsigned long** args_ *args_out = (uintptr_t*) emalloc(count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(count * sizeof(unsigned long)); - if (!*args_out || !*args_len_out) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } return 1; } @@ -920,14 +913,6 @@ int prepare_x_len_args(x_command_args_t* args, uintptr_t** args_out, unsigned lo *args_out = (uintptr_t*) emalloc(sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(sizeof(unsigned long)); - if (!*args_out || !*args_len_out) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set key as the only argument */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -950,14 +935,6 @@ int prepare_x_ack_args(x_command_args_t* args, uintptr_t** args_out, unsigned lo *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!*args_out || !*args_len_out) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set key as first argument */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -997,14 +974,6 @@ int prepare_x_del_args(x_command_args_t* args, uintptr_t** args_out, unsigned lo *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!*args_out || !*args_len_out) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set key as first argument */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -1045,15 +1014,6 @@ int prepare_x_range_args(x_command_args_t* args, *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - /* Check if memory allocation was successful */ - if (!*args_out || !*args_len_out) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set arguments */ unsigned int arg_idx = 0; @@ -1933,14 +1893,6 @@ int prepare_x_trim_args(x_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!*args_out || !*args_len_out) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set key as first argument */ unsigned int arg_idx = 0; (*args_out)[arg_idx] = (uintptr_t) args->key; diff --git a/valkey_glide_z_common.c b/valkey_glide_z_common.c index 403cab4c..cf166593 100644 --- a/valkey_glide_z_common.c +++ b/valkey_glide_z_common.c @@ -392,10 +392,7 @@ int execute_z_generic_command(valkey_glide_object* valkey_glide, case ZRem: case ZMScore: allocated_strings = (char**) emalloc(args->member_count * sizeof(char*)); - if (!allocated_strings) { - return 0; - } - arg_count = prepare_z_members_args( + arg_count = prepare_z_members_args( args, &arg_values, &arg_lens, &allocated_strings, &allocated_count); break; @@ -405,11 +402,8 @@ int execute_z_generic_command(valkey_glide_object* valkey_glide, case ZRangeByLex: case ZRevRangeByScore: case ZRevRangeByLex: - allocated_strings = - (char**) emalloc(10 * sizeof(char*)); /* Enough for typical options */ - if (!allocated_strings) { - return 0; - } + allocated_strings = (char**) emalloc(VALKEY_GLIDE_MAX_OPTIONS * sizeof(char*)); + arg_count = prepare_z_complex_range_args( args, &arg_values, cmd_type, &arg_lens, &allocated_strings, &allocated_count); cmd_type = ZRange; @@ -421,16 +415,6 @@ int execute_z_generic_command(valkey_glide_object* valkey_glide, arg_lens = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); allocated_strings = (char**) emalloc(1 * sizeof(char*)); - if (!arg_values || !arg_lens || !allocated_strings) { - if (arg_values) - efree(arg_values); - if (arg_lens) - efree(arg_lens); - if (allocated_strings) - efree(allocated_strings); - return 0; - } - /* Set arguments */ arg_values[0] = (uintptr_t) args->key; arg_lens[0] = args->key_len; @@ -440,12 +424,6 @@ int execute_z_generic_command(valkey_glide_object* valkey_glide, int increment_str_len = snprintf(increment_str, sizeof(increment_str), "%.6g", args->increment); char* increment_str_copy = estrndup(increment_str, increment_str_len); - if (!increment_str_copy) { - efree(arg_values); - efree(arg_lens); - efree(allocated_strings); - return 0; - } arg_values[1] = (uintptr_t) increment_str_copy; arg_lens[1] = increment_str_len; @@ -463,16 +441,6 @@ int execute_z_generic_command(valkey_glide_object* valkey_glide, arg_lens = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); allocated_strings = (char**) emalloc(2 * sizeof(char*)); - if (!arg_values || !arg_lens || !allocated_strings) { - if (arg_values) - efree(arg_values); - if (arg_lens) - efree(arg_lens); - if (allocated_strings) - efree(allocated_strings); - return 0; - } - /* Set arguments */ arg_values[0] = (uintptr_t) args->key; arg_lens[0] = args->key_len; @@ -484,14 +452,6 @@ int execute_z_generic_command(valkey_glide_object* valkey_glide, char* start_str_copy = estrndup(start_str, start_str_len); char* end_str_copy = estrndup(end_str, end_str_len); - if (!start_str_copy || !end_str_copy) { - if (start_str_copy) - efree(start_str_copy); - efree(arg_values); - efree(arg_lens); - efree(allocated_strings); - return 0; - } arg_values[1] = (uintptr_t) start_str_copy; arg_lens[1] = start_str_len; @@ -508,27 +468,21 @@ int execute_z_generic_command(valkey_glide_object* valkey_glide, case ZUnionStore: allocated_strings = (char**) emalloc(20 * sizeof(char*)); /* Enough for store commands */ - if (!allocated_strings) { - return 0; - } + arg_count = prepare_z_store_args( args, &arg_values, &arg_lens, &allocated_strings, &allocated_count); break; case ZInterCard: allocated_strings = (char**) emalloc(5 * sizeof(char*)); /* Enough for ZINTERCARD */ - if (!allocated_strings) { - return 0; - } + arg_count = prepare_z_intercard_args( args, &arg_values, &arg_lens, &allocated_strings, &allocated_count); break; case ZUnion: allocated_strings = (char**) emalloc(20 * sizeof(char*)); /* Enough for ZUNION */ - if (!allocated_strings) { - return 0; - } + arg_count = prepare_z_union_args( args, &arg_values, &arg_lens, &allocated_strings, &allocated_count); break; @@ -536,54 +490,41 @@ int execute_z_generic_command(valkey_glide_object* valkey_glide, case ZPopMax: case ZPopMin: allocated_strings = (char**) emalloc(2 * sizeof(char*)); /* Enough for ZPOP commands */ - if (!allocated_strings) { - return 0; - } - arg_count = prepare_z_pop_args( + arg_count = prepare_z_pop_args( args, &arg_values, &arg_lens, &allocated_strings, &allocated_count); break; case ZRangeStore: - allocated_strings = (char**) emalloc(10 * sizeof(char*)); /* Enough for ZRANGESTORE */ - if (!allocated_strings) { - return 0; - } + allocated_strings = (char**) emalloc(VALKEY_GLIDE_MAX_OPTIONS * sizeof(char*)); + arg_count = prepare_z_rangestore_args( args, &arg_values, &arg_lens, &allocated_strings, &allocated_count); break; case ZAdd: allocated_strings = (char**) emalloc(20 * sizeof(char*)); /* Enough for ZADD */ - if (!allocated_strings) { - return 0; - } + arg_count = prepare_z_zadd_args( args, &arg_values, &arg_lens, &allocated_strings, &allocated_count); break; case ZDiff: allocated_strings = (char**) emalloc(5 * sizeof(char*)); /* Enough for ZDIFF */ - if (!allocated_strings) { - return 0; - } + arg_count = prepare_z_zdiff_args( args, &arg_values, &arg_lens, &allocated_strings, &allocated_count); break; case ZInter: allocated_strings = (char**) emalloc(20 * sizeof(char*)); /* Enough for ZINTER */ - if (!allocated_strings) { - return 0; - } + arg_count = prepare_z_union_args( args, &arg_values, &arg_lens, &allocated_strings, &allocated_count); break; case ZRandMember: allocated_strings = (char**) emalloc(2 * sizeof(char*)); /* Enough for ZRANDMEMBER */ - if (!allocated_strings) { - return 0; - } + arg_count = prepare_z_randmember_args( args, &arg_values, &arg_lens, &allocated_strings, &allocated_count); break; @@ -591,10 +532,7 @@ int execute_z_generic_command(valkey_glide_object* valkey_glide, case BZPopMax: case BZPopMin: allocated_strings = (char**) emalloc(2 * sizeof(char*)); /* Enough for BZPOP commands */ - if (!allocated_strings) { - return 0; - } - arg_count = prepare_z_bzpop_args( + arg_count = prepare_z_bzpop_args( args, &arg_values, &arg_lens, &allocated_strings, &allocated_count); break; @@ -685,14 +623,6 @@ int prepare_z_key_args(z_command_args_t* args, uintptr_t** args_out, unsigned lo *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set arguments */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -720,14 +650,6 @@ int prepare_z_pop_args(z_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -735,11 +657,7 @@ int prepare_z_pop_args(z_command_args_t* args, char count_str[32]; snprintf(count_str, sizeof(count_str), "%ld", args->start); char* count_str_copy = estrdup(count_str); - if (!count_str_copy) { - efree(*args_out); - efree(*args_len_out); - return 0; - } + (*args_out)[1] = (uintptr_t) count_str_copy; (*args_len_out)[1] = strlen(count_str); (*allocated_strings)[(*allocated_count)++] = count_str_copy; @@ -767,14 +685,6 @@ int prepare_z_member_args(z_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set arguments */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -807,14 +717,6 @@ int prepare_z_range_args(z_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set arguments */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -849,14 +751,6 @@ int prepare_z_members_args(z_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* First argument: key */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -994,14 +888,6 @@ int prepare_z_complex_range_args(z_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* First argument: key */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -1113,14 +999,6 @@ int prepare_z_store_args(z_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set destination */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -1254,14 +1132,6 @@ int prepare_z_intercard_args(z_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Add numkeys as the first argument */ char numkeys_str[32]; snprintf(numkeys_str, sizeof(numkeys_str), "%d", args->member_count); @@ -1361,14 +1231,6 @@ int prepare_z_union_args(z_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Add numkeys as the first argument */ char numkeys_str[32]; snprintf(numkeys_str, sizeof(numkeys_str), "%d", args->member_count); @@ -1508,14 +1370,6 @@ int prepare_z_rangestore_args(z_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set dst and src (args->key is dst, args->member is src) */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -1619,14 +1473,6 @@ int prepare_z_zadd_args(z_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set key */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -1734,13 +1580,6 @@ int prepare_z_zdiff_args(z_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } /* Add numkeys as the first argument */ char numkeys_str[32]; @@ -1809,14 +1648,6 @@ int prepare_z_randmember_args(z_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set key */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -1894,14 +1725,6 @@ int prepare_z_bzpop_args(z_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Add keys as arguments based on format */ if (is_array_format) { /* Array format: iterate through the array */ From d971b9cea99351a5223e8deb96a78358d454c96d Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 18 Sep 2025 23:09:33 +0300 Subject: [PATCH 03/46] updates --- valkey_glide_commands_3.c | 49 ------------------------ valkey_glide_geo_common.c | 73 ++---------------------------------- valkey_glide_hash_common.c | 77 -------------------------------------- valkey_glide_list_common.c | 20 ---------- valkey_glide_x_common.c | 10 ----- valkey_glide_z.c | 8 ---- valkey_glide_z_common.c | 14 ------- 7 files changed, 4 insertions(+), 247 deletions(-) diff --git a/valkey_glide_commands_3.c b/valkey_glide_commands_3.c index 3fb26e61..69c41b71 100644 --- a/valkey_glide_commands_3.c +++ b/valkey_glide_commands_3.c @@ -199,14 +199,6 @@ int buffer_command_for_batch(valkey_glide_object* valkey_glide, cmd->args = (uint8_t**) emalloc(arg_count * sizeof(uint8_t*)); cmd->arg_lengths = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); - if (!cmd->args || !cmd->arg_lengths) { - if (cmd->args) - efree(cmd->args); - if (cmd->arg_lengths) - efree(cmd->arg_lengths); - return 0; - } - uintptr_t i; for (i = 0; i < arg_count; i++) { if (args[i] && arg_lengths[i] > 0) { @@ -273,15 +265,6 @@ static int convert_zval_args_to_strings(zval* args, *allocated_strings = (char**) emalloc(args_count * sizeof(char*)); *allocated_count = 0; - if (!*cmd_args || !*args_len || !*allocated_strings) { - if (*cmd_args) - efree(*cmd_args); - if (*args_len) - efree(*args_len); - if (*allocated_strings) - efree(*allocated_strings); - return 0; - } for (int i = 0; i < args_count; i++) { zval* arg = &args[i]; @@ -897,14 +880,6 @@ static int execute_fcall_command_internal(zval* object, uintptr_t* cmd_args = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); unsigned long* args_len = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!cmd_args || !args_len) { - if (cmd_args) - efree(cmd_args); - if (args_len) - efree(args_len); - return 0; - } - /* Set function name and numkeys */ cmd_args[0] = (uintptr_t) name; args_len[0] = name_len; @@ -1117,14 +1092,6 @@ int execute_restore_command(zval* object, int argc, zval* return_value, zend_cla uintptr_t* args = (uintptr_t*) emalloc(max_args * sizeof(uintptr_t)); unsigned long* args_len = (unsigned long*) emalloc(max_args * sizeof(unsigned long)); - if (!args || !args_len) { - if (args) - efree(args); - if (args_len) - efree(args_len); - return 0; - } - /* Set up base arguments */ args[0] = (uintptr_t) key; args_len[0] = key_len; @@ -1637,14 +1604,6 @@ int execute_rawcommand_command_internal( uintptr_t* cmd_args = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); unsigned long* args_len = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!cmd_args || !args_len) { - if (cmd_args) - efree(cmd_args); - if (args_len) - efree(args_len); - return 0; - } - /* Keep track of allocated strings for cleanup */ char** allocated = (char**) emalloc(args_count * sizeof(char*)); int allocated_idx = 0; @@ -1772,14 +1731,6 @@ int execute_client_command(zval* object, int argc, zval* return_value, zend_clas batch_args = (uintptr_t*) emalloc((arg_count - 1) * sizeof(uintptr_t)); arg_lengths = emalloc((arg_count - 1) * sizeof(unsigned long)); - if (!batch_args || !arg_lengths) { - if (batch_args) - efree(batch_args); - if (arg_lengths) - efree(arg_lengths); - return 0; - } - /* Convert each argument to string and store */ for (int i = 1; i < arg_count; i++) { zval* arg = &z_args[i]; diff --git a/valkey_glide_geo_common.c b/valkey_glide_geo_common.c index 55c396ee..afcbf6c0 100644 --- a/valkey_glide_geo_common.c +++ b/valkey_glide_geo_common.c @@ -63,14 +63,6 @@ int prepare_geo_members_args(geo_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* First argument: key */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -84,13 +76,6 @@ int prepare_geo_members_args(geo_command_args_t* args, str_val = zval_to_string_safe(member, &str_len, &need_free); - if (!str_val) { - /* Cleanup on error */ - free_allocated_strings(*allocated_strings, *allocated_count); - efree(*args_out); - efree(*args_len_out); - return 0; - } (*args_out)[i + 1] = (uintptr_t) str_val; (*args_len_out)[i + 1] = str_len; @@ -120,14 +105,6 @@ int prepare_geo_dist_args(geo_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Set arguments */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -170,14 +147,6 @@ int prepare_geo_add_args(geo_command_args_t* args, *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* First argument: key */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -191,14 +160,6 @@ int prepare_geo_add_args(geo_command_args_t* args, str_val = zval_to_string_safe(value, &str_len, &need_free); - if (!str_val) { - /* Cleanup on error */ - free_allocated_strings(*allocated_strings, *allocated_count); - efree(*args_out); - efree(*args_len_out); - return 0; - } - (*args_out)[i + 1] = (uintptr_t) str_val; (*args_len_out)[i + 1] = str_len; @@ -232,14 +193,6 @@ int prepare_geo_search_args(geo_command_args_t* args, *args_out = (uintptr_t*) emalloc(max_args * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(max_args * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Start building command arguments */ unsigned long arg_idx = 0; @@ -268,11 +221,7 @@ int prepare_geo_search_args(geo_command_args_t* args, /* Convert longitude and latitude to strings */ size_t lon_str_len, lat_str_len; char* lon_str = double_to_string(zval_get_double(lon), &lon_str_len); - if (!lon_str) { - efree(*args_out); - efree(*args_len_out); - return 0; - } + (*args_out)[arg_idx] = (uintptr_t) lon_str; (*args_len_out)[arg_idx++] = lon_str_len; (*allocated_strings)[(*allocated_count)++] = lon_str; @@ -298,15 +247,9 @@ int prepare_geo_search_args(geo_command_args_t* args, /* Convert radius to string */ size_t radius_str_len; - char* radius_str = double_to_string(*args->by_radius, &radius_str_len); - if (!radius_str) { - free_allocated_strings(*allocated_strings, *allocated_count); - efree(*args_out); - efree(*args_len_out); - return 0; - } - (*args_out)[arg_idx] = (uintptr_t) radius_str; - (*args_len_out)[arg_idx++] = radius_str_len; + char* radius_str = double_to_string(*args->by_radius, &radius_str_len); + (*args_out)[arg_idx] = (uintptr_t) radius_str; + (*args_len_out)[arg_idx++] = radius_str_len; (*allocated_strings)[(*allocated_count)++] = radius_str; (*args_out)[arg_idx] = (uintptr_t) args->unit; @@ -385,14 +328,6 @@ int prepare_geo_search_store_args(geo_command_args_t* args, *args_out = (uintptr_t*) emalloc(max_args * sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(max_args * sizeof(unsigned long)); - if (!(*args_out) || !(*args_len_out)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - return 0; - } - /* Start building command arguments */ unsigned long arg_idx = 0; diff --git a/valkey_glide_hash_common.c b/valkey_glide_hash_common.c index 466b0d36..50243a81 100644 --- a/valkey_glide_hash_common.c +++ b/valkey_glide_hash_common.c @@ -320,16 +320,6 @@ int prepare_h_multi_field_args(h_command_args_t* args, *allocated_strings = (char**) emalloc(args->field_count * sizeof(char*)); *allocated_count = 0; - if (!*args_out || !*args_len_out || !*allocated_strings) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - if (*allocated_strings) - efree(*allocated_strings); - return 0; - } - /* Set key as first argument */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -377,16 +367,6 @@ int prepare_h_set_args(h_command_args_t* args, *allocated_strings = (char**) emalloc((pairs_count * 2) * sizeof(char*)); *allocated_count = 0; - if (!*args_out || !*args_len_out || !*allocated_strings) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - if (*allocated_strings) - efree(*allocated_strings); - return 0; - } - /* First argument: key */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -407,16 +387,6 @@ int prepare_h_set_args(h_command_args_t* args, *allocated_strings = (char**) emalloc(args->fv_count * sizeof(char*)); *allocated_count = 0; - if (!*args_out || !*args_len_out || !*allocated_strings) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - if (*allocated_strings) - efree(*allocated_strings); - return 0; - } - /* First argument: key */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -454,16 +424,6 @@ int prepare_h_mset_args(h_command_args_t* args, *allocated_strings = (char**) emalloc((pairs_count * 2) * sizeof(char*)); *allocated_count = 0; - if (!*args_out || !*args_len_out || !*allocated_strings) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - if (*allocated_strings) - efree(*allocated_strings); - return 0; - } - /* First argument: key */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -491,16 +451,6 @@ int prepare_h_incr_args(h_command_args_t* args, *allocated_strings = (char**) emalloc(sizeof(char*)); *allocated_count = 0; - if (!*args_out || !*args_len_out || !*allocated_strings) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - if (*allocated_strings) - efree(*allocated_strings); - return 0; - } - /* Set key and field arguments */ (*args_out)[0] = (uintptr_t) args->key; (*args_len_out)[0] = args->key_len; @@ -519,13 +469,6 @@ int prepare_h_incr_args(h_command_args_t* args, incr_str = long_to_string(args->increment, &incr_len); } - if (!incr_str) { - efree(*args_out); - efree(*args_len_out); - efree(*allocated_strings); - return 0; - } - (*allocated_strings)[0] = incr_str; *allocated_count = 1; @@ -564,16 +507,6 @@ int prepare_h_randfield_args(h_command_args_t* args, *allocated_strings = need_count_str ? (char**) emalloc(sizeof(char*)) : NULL; *allocated_count = 0; - if (!*args_out || !*args_len_out || (need_count_str && !*allocated_strings)) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - if (*allocated_strings) - efree(*allocated_strings); - return 0; - } - /* First argument: key */ int arg_idx = 0; (*args_out)[arg_idx] = (uintptr_t) args->key; @@ -583,13 +516,6 @@ int prepare_h_randfield_args(h_command_args_t* args, /* Add count if needed */ if (need_count_str) { char* count_str = long_to_string(args->count, &(*args_len_out)[arg_idx]); - if (!count_str) { - efree(*args_out); - efree(*args_len_out); - if (*allocated_strings) - efree(*allocated_strings); - return 0; - } (*allocated_strings)[0] = count_str; *allocated_count = 1; @@ -864,9 +790,6 @@ int convert_zval_array_to_args(zval* z_array, int need_free; char* str_val = zval_to_string_safe(value, &str_len, &need_free); - if (!str_val) { - return 0; - } args[current_arg] = (uintptr_t) str_val; args_len[current_arg] = str_len; diff --git a/valkey_glide_list_common.c b/valkey_glide_list_common.c index 58eb94d7..4d8358ec 100644 --- a/valkey_glide_list_common.c +++ b/valkey_glide_list_common.c @@ -482,11 +482,6 @@ int prepare_list_key_values_args(list_command_args_t* args, /* Convert long to string */ size_t str_len; char* str_val = alloc_list_number_string(Z_LVAL_P(value), &str_len); - if (!str_val) { - FREE_LIST_ALLOCATED_STRINGS(*allocated_strings, *allocated_count); - free_list_command_args(*args_out, *args_len_out); - return 0; - } (*allocated_strings)[*allocated_count] = str_val; (*allocated_count)++; @@ -498,11 +493,6 @@ int prepare_list_key_values_args(list_command_args_t* args, /* Convert double to string */ size_t str_len; char* str_val = alloc_list_double_string(Z_DVAL_P(value), &str_len); - if (!str_val) { - FREE_LIST_ALLOCATED_STRINGS(*allocated_strings, *allocated_count); - free_list_command_args(*args_out, *args_len_out); - return 0; - } (*allocated_strings)[*allocated_count] = str_val; (*allocated_count)++; @@ -523,11 +513,6 @@ int prepare_list_key_values_args(list_command_args_t* args, /* Convert long to string */ size_t str_len; char* str_val = alloc_list_number_string(Z_LVAL_P(z_item), &str_len); - if (!str_val) { - FREE_LIST_ALLOCATED_STRINGS(*allocated_strings, *allocated_count); - free_list_command_args(*args_out, *args_len_out); - return 0; - } (*allocated_strings)[*allocated_count] = str_val; (*allocated_count)++; @@ -539,11 +524,6 @@ int prepare_list_key_values_args(list_command_args_t* args, /* Convert double to string */ size_t str_len; char* str_val = alloc_list_double_string(Z_DVAL_P(z_item), &str_len); - if (!str_val) { - FREE_LIST_ALLOCATED_STRINGS(*allocated_strings, *allocated_count); - free_list_command_args(*args_out, *args_len_out); - return 0; - } (*allocated_strings)[*allocated_count] = str_val; (*allocated_count)++; diff --git a/valkey_glide_x_common.c b/valkey_glide_x_common.c index 58853d6b..7ec56192 100644 --- a/valkey_glide_x_common.c +++ b/valkey_glide_x_common.c @@ -1098,16 +1098,6 @@ int prepare_x_add_args(x_command_args_t* args, *allocated_strings = (char**) ecalloc(args->fv_count + 5, sizeof(char*)); *allocated_count = 0; - if (!*args_out || !*args_len_out || !*allocated_strings) { - if (*args_out) - efree(*args_out); - if (*args_len_out) - efree(*args_len_out); - if (*allocated_strings) - efree(*allocated_strings); - return 0; - } - /* Set key as first argument */ unsigned int arg_idx = 0; (*args_out)[arg_idx] = (uintptr_t) args->key; diff --git a/valkey_glide_z.c b/valkey_glide_z.c index e2596c74..2e1f0d81 100644 --- a/valkey_glide_z.c +++ b/valkey_glide_z.c @@ -1582,14 +1582,6 @@ int prepare_mpop_arguments(const void* glide_client, uintptr_t* args = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); unsigned long* args_len = (unsigned long*) emalloc(arg_count * sizeof(unsigned long)); - if (!args || !args_len) { - if (args) - efree(args); - if (args_len) - efree(args_len); - return 0; - } - /* Current argument index */ int arg_idx = 0; diff --git a/valkey_glide_z_common.c b/valkey_glide_z_common.c index cf166593..d802f21c 100644 --- a/valkey_glide_z_common.c +++ b/valkey_glide_z_common.c @@ -771,16 +771,6 @@ int prepare_z_members_args(z_command_args_t* args, str_val = zval_to_string_safe(z_member, &str_len, &need_free); - if (!str_val) { - int j; - for (j = 0; j < *allocated_count; j++) { - efree((*allocated_strings)[j]); - } - efree(*args_out); - efree(*args_len_out); - return 0; - } - (*args_out)[i + 1] = (uintptr_t) str_val; (*args_len_out)[i + 1] = str_len; @@ -813,10 +803,6 @@ static int convert_zval_to_string_arg(zval* z_value, str_val = zval_to_string_safe(z_value, &str_len, &need_free); - if (!str_val) { - return 0; - } - *arg_ptr = (uintptr_t) str_val; *arg_len_ptr = str_len; From 5bc17940cfd8afc4250ac1e4c7105409b923ac83 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 21 Sep 2025 13:23:41 +0300 Subject: [PATCH 04/46] Tests are passing --- tests/ValkeyGlideTest.php | 604 ++++++++++++++++++++++++++++++++++++ valkey_glide_geo_commands.c | 177 +---------- valkey_glide_geo_common.c | 526 +++++++++++++++++++++++++++++++ valkey_glide_geo_common.h | 45 +++ 4 files changed, 1179 insertions(+), 173 deletions(-) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 499f5b08..db69d12e 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -5152,6 +5152,610 @@ public function testGeoSearchStore() $this->assertEquals(['Chico'], $this->valkey_glide->geosearch('{gk}dst', 'Chico', 1, 'm')); } + /** + * Add test cities with known coordinates for testing + */ + private function addTestCities() + { + // Clear any existing data + $this->valkey_glide->del('geo_test_key'); + $this->valkey_glide->del('geo_src_key'); + $this->valkey_glide->del('geo_dst_key'); + + // Add cities with their coordinates to source key + $this->valkey_glide->geoadd('geo_src_key', + -121.837478, 39.728494, 'Chico', // Northern California + -121.494400, 38.581572, 'Sacramento', // Central California + -121.693583, 39.363777, 'Gridley', // Near Chico + -121.591355, 39.145725, 'Marysville', // Between Chico and Sacramento + -122.032182, 37.322998, 'Cupertino' // Bay Area + ); + + // Add cities with their coordinates + $this->valkey_glide->geoadd('geo_test_key', + -121.837478, 39.728494, 'Chico', // Northern California + -121.494400, 38.581572, 'Sacramento', // Central California + -121.693583, 39.363777, 'Gridley', // Near Chico + -121.591355, 39.145725, 'Marysville', // Between Chico and Sacramento + -122.032182, 37.322998, 'Cupertino' // Bay Area + ); + + } + + public function testGeoSearchStoreBasicRadius() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test basic radius search and store + $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', 'Chico', 50, 'km'); + $this->assertIsInt($count); + $this->assertGTE(2, $count); // Should find at least Chico and Gridley + + // Verify the destination key was created and contains expected members + $members = $this->valkey_glide->zrange('geo_dst_key', 0, -1); + $this->assertIsArray($members); + $this->assertContains('Chico', $members); + $this->assertContains('Gridley', $members); + } + + public function testGeoSearchStoreFromCoordinates() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test search and store from longitude/latitude coordinates + $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + [-121.837478, 39.728494], 50, 'km'); + $this->assertIsInt($count); + $this->assertGTE(2, $count); + + // Verify stored results + $members = $this->valkey_glide->zrange('geo_dst_key', 0, -1); + $this->assertContains('Chico', $members); + $this->assertContains('Gridley', $members); + } + + public function testGeoSearchStoreByBox() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test rectangular search and store (BYBOX) + $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + 'Sacramento', [100, 100], 'km'); + $this->assertIsInt($count); + $this->assertGTE(1, $count); + + // Verify stored results + $members = $this->valkey_glide->zrange('geo_dst_key', 0, -1); + $this->assertContains('Sacramento', $members); + } + + public function testGeoSearchStoreWithCount() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test COUNT option + $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + 'Sacramento', 200, 'km', ['count' => 2]); + $this->assertIsInt($count); + $this->assertLTE(2, $count); + + // Verify stored count matches returned count + $storedCount = $this->valkey_glide->zcard('geo_dst_key'); + $this->assertEquals($count, $storedCount); + } + + public function testGeoSearchStoreWithCountAny() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test COUNT with ANY option + $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + 'Sacramento', 200, 'km', ['count' => [3, 'ANY']]); + $this->assertIsInt($count); + $this->assertLTE(3, $count); + + // Verify stored count + $storedCount = $this->valkey_glide->zcard('geo_dst_key'); + $this->assertEquals($count, $storedCount); + } + + public function testGeoSearchStoreWithSort() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test ASC sorting + $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + 'Sacramento', 200, 'km', ['sort' => 'ASC']); + $this->assertIsInt($count); + $this->assertGTE(1, $count); + + // Verify results are stored + $storedCount = $this->valkey_glide->zcard('geo_dst_key'); + $this->assertEquals($count, $storedCount); + } + + public function testGeoSearchStoreWithStoreDist() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test STOREDIST option - stores distances instead of geohashes + $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + 'Chico', 50, 'km', ['storedist' => true]); + $this->assertIsInt($count); + $this->assertGTE(2, $count); + + // Verify the destination key contains distance scores + $membersWithScores = $this->valkey_glide->zrange('geo_dst_key', 0, -1, ['withscores' => true]); + $this->assertIsArray($membersWithScores); + + // Check that scores are distances (should be reasonable values) + foreach ($membersWithScores as $member => $score) { + $this->assertIsString($member); + $this->assertIsFloat($score); + $this->assertGTE(0, $score); // Distance should be non-negative + $this->assertLTE(100, $score); // Should be within our search radius + } + } + + public function testGeoSearchStoreComplexQuery() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Complex query: box search from coordinates with multiple options + $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + [-121.5, 38.5], // coordinates + [200, 200], // box dimensions + 'km', + [ + 'count' => [3, 'ANY'], + 'sort' => 'DESC', + 'storedist' => true + ] + ); + + $this->assertIsInt($count); + $this->assertLTE(3, $count); + + // Verify stored results + $storedCount = $this->valkey_glide->zcard('geo_dst_key'); + $this->assertEquals($count, $storedCount); + + // Verify distances are stored as scores + $membersWithScores = $this->valkey_glide->zrange('geo_dst_key', 0, -1, ['withscores' => true]); + foreach ($membersWithScores as $member => $score) { + $this->assertIsFloat($score); + $this->assertGTE(0, $score); + } + } + + public function testGeoSearchStoreDifferentUnits() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test different units + $units = ['m', 'km', 'ft', 'mi']; + + foreach ($units as $unit) { + $dstKey = "geo_dst_key_$unit"; + $count = $this->valkey_glide->geosearchstore($dstKey, 'geo_src_key', + 'Chico', 50000, $unit); + $this->assertIsInt($count); + $this->assertGTE(1, $count); + + // Clean up + $this->valkey_glide->del($dstKey); + } + } + + public function testGeoSearchStoreOverwriteDestination() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); + } + $this->addTestCities(); + // First search and store + $count1 = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + 'Chico', 30, 'km'); + $this->assertIsInt($count1); + + // Second search and store to same destination (should overwrite) + $count2 = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + 'Sacramento', 100, 'km'); + $this->assertIsInt($count2); + + // Verify the destination was overwritten + $finalCount = $this->valkey_glide->zcard('geo_dst_key'); + $this->assertEquals($count2, $finalCount); + + // Verify it contains Sacramento results, not Chico results + $members = $this->valkey_glide->zrange('geo_dst_key', 0, -1); + $this->assertContains('Sacramento', $members); + } + + public function testGeoSearchStoreEmptyResult() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Search in area with no cities + $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + [0, 0], 1, 'km'); + $this->assertIsInt($count); + $this->assertEquals(0, $count); + + // Verify destination key is empty or doesn't exist + $storedCount = $this->valkey_glide->zcard('geo_dst_key'); + $this->assertEquals(0, $storedCount); + } + + public function testGeoSearchStoreNonExistentSource() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Search on non-existent source key + $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'non_existent_key', + 'member', 100, 'km'); + $this->assertIsInt($count); + $this->assertEquals(0, $count); + + // Verify destination key is empty + $storedCount = $this->valkey_glide->zcard('geo_dst_key'); + $this->assertEquals(0, $storedCount); + } + + public function testGeoSearchStoreReturnValue() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test that return value matches actual stored count + $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + 'Sacramento', 200, 'km'); + + $actualCount = $this->valkey_glide->zcard('geo_dst_key'); + $this->assertEquals($count, $actualCount); + + // Test with COUNT limit + $limitedCount = $this->valkey_glide->geosearchstore('geo_dst_key2', 'geo_src_key', + 'Sacramento', 200, 'km', ['count' => 2]); + + $actualLimitedCount = $this->valkey_glide->zcard('geo_dst_key2'); + $this->assertEquals($limitedCount, $actualLimitedCount); + $this->assertLTE(2, $limitedCount); + + // Clean up + $this->valkey_glide->del('geo_dst_key2'); + } + + public function testGeoSearchStorePreservesGeoData() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Store without STOREDIST (should preserve geo data) + $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + 'Chico', 50, 'km'); + $this->assertGTE(2, $count); + + // Verify we can perform geo operations on the stored data + $distance = $this->valkey_glide->geodist('geo_dst_key', 'Chico', 'Gridley', 'km'); + $this->assertIsFloat($distance); + $this->assertGTE(0, $distance); + + // Verify geopos works on stored data + $positions = $this->valkey_glide->geopos('geo_dst_key', 'Chico'); + $this->assertIsArray($positions); + $this->assertCount(1, $positions); + $this->assertIsArray($positions[0]); + $this->assertCount(2, $positions[0]); + } + + public function testGeoSearchBasicRadius() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test basic radius search from member + $result = $this->valkey_glide->geosearch('geo_test_key', 'Chico', 50, 'km'); + $this->assertIsArray($result); + $this->assertContains('Chico', $result); + $this->assertContains('Gridley', $result); + + // Should not contain distant cities + $this->assertFalse(in_array('Cupertino', $result)); + } + + public function testGeoSearchFromCoordinates() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test search from longitude/latitude coordinates + $result = $this->valkey_glide->geosearch('geo_test_key', [-121.837478, 39.728494], 50, 'km'); + $this->assertIsArray($result); + $this->assertContains('Chico', $result); + $this->assertContains('Gridley', $result); + } + + public function testGeoSearchByBox() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test rectangular search (BYBOX) + $result = $this->valkey_glide->geosearch('geo_test_key', 'Sacramento', [150, 150], 'km'); + $this->assertIsArray($result); + $this->assertContains('Sacramento', $result); + + // Should contain cities within the box + $this->assertContains('Marysville', $result); + } + + public function testGeoSearchWithCoord() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + $result = $this->valkey_glide->geosearch('geo_test_key', 'Chico', 50, 'km', ['withcoord']); + $this->assertIsArray($result); + + foreach ($result as $city => $data) { + $this->assertIsString($city); + $this->assertIsArray($data); + $this->assertCount(1, $data); // Only coordinates + $this->assertIsArray($data[0]); // Coordinates array + $this->assertCount(2, $data[0]); // [longitude, latitude] + $this->assertIsFloat($data[0][0]); // longitude + $this->assertIsFloat($data[0][1]); // latitude + } + } + + public function testGeoSearchWithDist() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + $result = $this->valkey_glide->geosearch('geo_test_key', 'Chico', 50, 'km', ['withdist']); + $this->assertIsArray($result); + + foreach ($result as $city => $data) { + $this->assertIsString($city); + $this->assertIsArray($data); + $this->assertCount(1, $data); // Only distance + $this->assertIsFloat($data[0]); // Distance value + $this->assertGTE(0, $data[0]); // Distance should be non-negative + } + } + + public function testGeoSearchWithHash() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + $result = $this->valkey_glide->geosearch('geo_test_key', 'Chico', 50, 'km', ['withhash']); + $this->assertIsArray($result); + + foreach ($result as $city => $data) { + $this->assertIsString($city); + $this->assertIsArray($data); + $this->assertCount(1, $data); // Only hash + $this->assertIsInt($data[0]); // Hash value + } + } + + public function testGeoSearchWithAllOptions() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + $result = $this->valkey_glide->geosearch('geo_test_key', 'Chico', 50, 'km', + ['withdist', 'withhash', 'withcoord']); + $this->assertIsArray($result); + + foreach ($result as $city => $data) { + $this->assertIsString($city); + $this->assertIsArray($data); + $this->assertCount(3, $data); // distance, hash, coordinates + $this->assertIsFloat($data[0]); // Distance + $this->assertIsInt($data[1]); // Hash + $this->assertIsArray($data[2]); // Coordinates + $this->assertCount(2, $data[2]); // [longitude, latitude] + } + } + + public function testGeoSearchWithCount() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test COUNT option + $result = $this->valkey_glide->geosearch('geo_test_key', 'Sacramento', 200, 'km', + ['count' => 2]); + $this->assertIsArray($result); + $this->assertLTE(2, count($result)); + } + + public function testGeoSearchWithCountAny() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test COUNT with ANY option + $result = $this->valkey_glide->geosearch('geo_test_key', 'Sacramento', 200, 'km', + ['count' => [2, 'ANY']]); + $this->assertIsArray($result); + $this->assertLTE(2, count($result)); + } + + public function testGeoSearchWithSortAsc() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test ASC sorting with distances + $result = $this->valkey_glide->geosearch('geo_test_key', 'Sacramento', 200, 'km', + ['withdist', 'asc']); + $this->assertIsArray($result); + + // Verify distances are in ascending order + $distances = []; + foreach ($result as $city => $data) { + $distances[] = $data[0]; + } + + $sortedDistances = $distances; + sort($sortedDistances); + $this->assertEquals($sortedDistances, $distances); + } + + public function testGeoSearchWithSortDesc() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + + // Test DESC sorting with distances + $result = $this->valkey_glide->geosearch('geo_test_key', 'Sacramento', 200, 'km', + ['withdist', 'desc']); + $this->assertIsArray($result); + + // Verify distances are in descending order + $distances = []; + foreach ($result as $city => $data) { + $distances[] = $data[0]; + } + + $sortedDistances = $distances; + rsort($sortedDistances); + $this->assertEquals($sortedDistances, $distances); + } + + public function testGeoSearchAlternativeSortSyntax() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test alternative sort syntax using 'sort' key + $result = $this->valkey_glide->geosearch('geo_test_key', 'Sacramento', 200, 'km', + ['withdist', 'sort' => 'ASC']); + $this->assertIsArray($result); + + // Verify distances are in ascending order + $distances = []; + foreach ($result as $city => $data) { + $distances[] = $data[0]; + } + + $sortedDistances = $distances; + sort($sortedDistances); + $this->assertEquals($sortedDistances, $distances); + } + + public function testGeoSearchDifferentUnits() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Test different units + $units = ['m', 'km', 'ft', 'mi']; + + foreach ($units as $unit) { + $result = $this->valkey_glide->geosearch('geo_test_key', 'Chico', 50000, $unit); + $this->assertIsArray($result); + $this->assertTrue(count($result) > 0); + } + } + + public function testGeoSearchComplexQuery() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Complex query: box search from coordinates with all options + $result = $this->valkey_glide->geosearch('geo_test_key', + [-121.5, 38.5], // coordinates + [200, 200], // box dimensions + 'km', + [ + 'withdist', + 'withcoord', + 'withhash', + 'count' => [3, 'ANY'], + 'sort' => 'ASC' + ] + ); + + $this->assertIsArray($result); + $this->assertLTE(3, count($result)); + + foreach ($result as $city => $data) { + $this->assertIsString($city); + $this->assertIsArray($data); + $this->assertCount(3, $data); // distance, hash, coordinates + } + } + + public function testGeoSearchEmptyResult() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + $this->addTestCities(); + // Search in area with no cities + $result = $this->valkey_glide->geosearch('geo_test_key', [0, 0], 1, 'km'); + $this->assertIsArray($result); + $this->assertEquals(0, count($result)); + } + + public function testGeoSearchNonExistentKey() + { + if (!$this->minVersionCheck('6.2.0')) { + $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); + } + + // Search on non-existent key + $result = $this->valkey_glide->geosearch('non_existent_key', 'member', 100, 'km'); + $this->assertIsArray($result); + $this->assertEquals(0, count($result)); + } + /* Test a 'raw' command */ public function testRawCommand() { diff --git a/valkey_glide_geo_commands.c b/valkey_glide_geo_commands.c index 718f5a21..ba5e72eb 100644 --- a/valkey_glide_geo_commands.c +++ b/valkey_glide_geo_commands.c @@ -232,184 +232,15 @@ int execute_geopos_command(zval* object, int argc, zval* return_value, zend_clas return result; } -/* GEOSEARCH implementation */ +/* GEOSEARCH implementation - now uses unified function */ int execute_geosearch_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { - char * key = NULL, *unit = NULL; - size_t key_len, unit_len; - zval* from = NULL; - double radius; - zval* options = NULL; - const void* glide_client = NULL; - - /* Parse parameters for simple case: geosearch(key, member, radius, unit [, options]) */ - if (zend_parse_method_parameters(argc, - object, - "Oszds|a", - &object, - ce, - &key, - &key_len, - &from, - &radius, - &unit, - &unit_len, - &options) == FAILURE) { - return 0; - } - - /* Get ValkeyGlide object */ - valkey_glide_object* valkey_glide = - VALKEY_GLIDE_PHP_ZVAL_GET_OBJECT(valkey_glide_object, object); - glide_client = valkey_glide->glide_client; - - /* Check if we have a valid glide client */ - if (!glide_client || !key || !from) { - return 0; - } - - /* Initialize geo command arguments structure */ - geo_command_args_t args = {0}; - args.key = key; - args.key_len = key_len; - args.from = from; - args.by_radius = &radius; - args.unit = unit; - args.unit_len = unit_len; - args.options = options; - - /* Parse the WITH* options if provided */ - if (options && Z_TYPE_P(options) == IS_ARRAY) { - HashTable* ht = Z_ARRVAL_P(options); - zval* opt; - - ZEND_HASH_FOREACH_VAL(ht, opt) { - if (Z_TYPE_P(opt) == IS_STRING) { - if (strcasecmp(Z_STRVAL_P(opt), "withcoord") == 0) { - args.radius_opts.with_opts.withcoord = 1; - } else if (strcasecmp(Z_STRVAL_P(opt), "withdist") == 0) { - args.radius_opts.with_opts.withdist = 1; - } else if (strcasecmp(Z_STRVAL_P(opt), "withhash") == 0) { - args.radius_opts.with_opts.withhash = 1; - } - } - } - ZEND_HASH_FOREACH_END(); - } - - /* Create a data structure to pass the WITH* options to the result processor */ - typedef struct { - int withcoord; - int withdist; - int withhash; - } search_data_t; - - search_data_t* search_data = emalloc(sizeof(search_data_t)); - - search_data->withcoord = args.radius_opts.with_opts.withcoord; - search_data->withdist = args.radius_opts.with_opts.withdist; - search_data->withhash = args.radius_opts.with_opts.withhash; - - /* Execute the generic command with appropriate result processor */ - int result = execute_geo_generic_command( - valkey_glide, GeoSearch, &args, search_data, process_geo_search_result_async, return_value); - if (result == 0) { - efree(search_data); - return result; - } - /* Handle batch mode return value */ - if (valkey_glide->is_in_batch_mode) { - ZVAL_COPY(return_value, object); - return 1; - } - return result; + return execute_geosearch_unified(object, argc, return_value, ce, 0); } -/* GEOSEARCHSTORE implementation */ +/* GEOSEARCHSTORE implementation - now uses unified function */ int execute_geosearchstore_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { - char * dest = NULL, *src = NULL, *unit = NULL; - size_t dest_len, src_len, unit_len; - zval* from; - double radius; - zval* options = NULL; - const void* glide_client = NULL; - - /* Parse parameters */ - if (zend_parse_method_parameters(argc, - object, - "Osszds|a", - &object, - ce, - &dest, - &dest_len, - &src, - &src_len, - &from, - &radius, - &unit, - &unit_len, - &options) == FAILURE) { - return 0; - } - - /* Get ValkeyGlide object */ - valkey_glide_object* valkey_glide = - VALKEY_GLIDE_PHP_ZVAL_GET_OBJECT(valkey_glide_object, object); - glide_client = valkey_glide->glide_client; - - /* Check if we have a valid glide client */ - if (!glide_client || !dest || !src || !from) { - return 0; - } - - /* Initialize geo command arguments structure */ - geo_command_args_t args = {0}; - args.dest = dest; - args.dest_len = dest_len; - args.src = src; - args.src_len = src_len; - args.from = from; - args.by_radius = &radius; - args.unit = unit; - args.unit_len = unit_len; - args.options = options; - - /* Parse options if provided */ - if (options && Z_TYPE_P(options) == IS_ARRAY) { - HashTable* ht_options = Z_ARRVAL_P(options); - zval* opt_val; - - /* COUNT option */ - if ((opt_val = zend_hash_str_find(ht_options, "count", sizeof("count") - 1)) != NULL) { - args.radius_opts.count = zval_get_long(opt_val); - } - - /* SORT option (ASC/DESC) */ - if ((opt_val = zend_hash_str_find(ht_options, "sort", sizeof("sort") - 1)) != NULL) { - if (Z_TYPE_P(opt_val) == IS_STRING) { - args.radius_opts.sort = Z_STRVAL_P(opt_val); - args.radius_opts.sort_len = Z_STRLEN_P(opt_val); - } - } - - /* STOREDIST option */ - if ((opt_val = zend_hash_str_find(ht_options, "storedist", sizeof("storedist") - 1)) != - NULL) { - args.radius_opts.store_dist = zval_is_true(opt_val); - } - } - - /* Execute the generic command with appropriate result processor */ - int result = execute_geo_generic_command( - valkey_glide, GeoSearchStore, &args, NULL, process_geo_int_result_async, return_value); - - /* Handle batch mode return value */ - if (valkey_glide->is_in_batch_mode) { - ZVAL_COPY(return_value, object); - return 1; - } - - return result; + return execute_geosearch_unified(object, argc, return_value, ce, 1); } diff --git a/valkey_glide_geo_common.c b/valkey_glide_geo_common.c index afcbf6c0..018bec27 100644 --- a/valkey_glide_geo_common.c +++ b/valkey_glide_geo_common.c @@ -845,3 +845,529 @@ int execute_geo_generic_command(valkey_glide_object* valkey_glide, return success; } + +/* ==================================================================== + * UNIFIED GEOSEARCH/GEOSEARCHSTORE IMPLEMENTATION + * ==================================================================== */ + +/** + * Parse parameters for GEOSEARCH/GEOSEARCHSTORE commands with flexible API support + */ +int parse_geosearch_parameters(int argc, + zval* object, + zend_class_entry* ce, + geo_search_params_t* params, + int is_store_variant) { + zval* position = NULL; + zval* shape = NULL; + zval* options = NULL; + + /* Initialize parameters structure */ + memset(params, 0, sizeof(geo_search_params_t)); + + if (is_store_variant) { + /* GEOSEARCHSTORE: (object, dest, src, position, shape, unit [, options]) */ + char * dest = NULL, *src = NULL, *unit = NULL; + size_t dest_len, src_len, unit_len; + + if (zend_parse_method_parameters(argc, + object, + "Osszzs|a", + &object, + ce, + &dest, + &dest_len, + &src, + &src_len, + &position, + &shape, + &unit, + &unit_len, + &options) == FAILURE) { + return 0; + } + + params->key = dest; + params->key_len = dest_len; + params->src_key = src; + params->src_key_len = src_len; + params->unit = unit; + params->unit_len = unit_len; + } else { + /* GEOSEARCH: (object, key, position, shape, unit [, options]) */ + char * key = NULL, *unit = NULL; + size_t key_len, unit_len; + + if (zend_parse_method_parameters(argc, + object, + "Oszzs|a", + &object, + ce, + &key, + &key_len, + &position, + &shape, + &unit, + &unit_len, + &options) == FAILURE) { + return 0; + } + + params->key = key; + params->key_len = key_len; + params->unit = unit; + params->unit_len = unit_len; + } + + /* Parse position parameter */ + if (Z_TYPE_P(position) == IS_STRING) { + /* FROMMEMBER */ + params->is_from_member = 1; + params->member = Z_STRVAL_P(position); + params->member_len = Z_STRLEN_P(position); + } else if (Z_TYPE_P(position) == IS_ARRAY) { + /* FROMLONLAT */ + HashTable* pos_ht = Z_ARRVAL_P(position); + if (zend_hash_num_elements(pos_ht) != 2) { + php_error_docref( + NULL, + E_WARNING, + "Position array must contain exactly 2 elements [longitude, latitude]"); + return 0; + } + + zval *lon_val, *lat_val; + lon_val = zend_hash_index_find(pos_ht, 0); + lat_val = zend_hash_index_find(pos_ht, 1); + + if (!lon_val || !lat_val) { + php_error_docref( + NULL, E_WARNING, "Position array must contain longitude and latitude values"); + return 0; + } + + params->is_from_member = 0; + params->longitude = zval_get_double(lon_val); + params->latitude = zval_get_double(lat_val); + } else { + php_error_docref( + NULL, + E_WARNING, + "Position must be either a string (member) or array [longitude, latitude]"); + return 0; + } + + /* Parse shape parameter */ + if (Z_TYPE_P(shape) == IS_LONG || Z_TYPE_P(shape) == IS_DOUBLE) { + /* BYRADIUS */ + params->is_by_radius = 1; + params->radius = zval_get_double(shape); + } else if (Z_TYPE_P(shape) == IS_ARRAY) { + /* BYBOX */ + HashTable* shape_ht = Z_ARRVAL_P(shape); + if (zend_hash_num_elements(shape_ht) != 2) { + php_error_docref( + NULL, E_WARNING, "Shape array must contain exactly 2 elements [width, height]"); + return 0; + } + + zval *width_val, *height_val; + width_val = zend_hash_index_find(shape_ht, 0); + height_val = zend_hash_index_find(shape_ht, 1); + + if (!width_val || !height_val) { + php_error_docref(NULL, E_WARNING, "Shape array must contain width and height values"); + return 0; + } + + params->is_by_radius = 0; + params->width = zval_get_double(width_val); + params->height = zval_get_double(height_val); + } else { + php_error_docref( + NULL, E_WARNING, "Shape must be either a number (radius) or array [width, height]"); + return 0; + } + + /* Parse options if provided */ + if (options && Z_TYPE_P(options) == IS_ARRAY) { + HashTable* ht = Z_ARRVAL_P(options); + zval* opt; + + /* Parse array-based options (WITHCOORD, WITHDIST, WITHHASH) */ + ZEND_HASH_FOREACH_VAL(ht, opt) { + if (Z_TYPE_P(opt) == IS_STRING) { + if (strcasecmp(Z_STRVAL_P(opt), "withcoord") == 0) { + params->options.with_opts.withcoord = 1; + } else if (strcasecmp(Z_STRVAL_P(opt), "withdist") == 0) { + params->options.with_opts.withdist = 1; + } else if (strcasecmp(Z_STRVAL_P(opt), "withhash") == 0) { + params->options.with_opts.withhash = 1; + } else if (strcasecmp(Z_STRVAL_P(opt), "asc") == 0) { + params->options.sort = "ASC"; + params->options.sort_len = 3; + } else if (strcasecmp(Z_STRVAL_P(opt), "desc") == 0) { + params->options.sort = "DESC"; + params->options.sort_len = 4; + } + } + } + ZEND_HASH_FOREACH_END(); + + /* Parse key-based options */ + zval* opt_val; + + /* COUNT option */ + if ((opt_val = zend_hash_str_find(ht, "count", sizeof("count") - 1)) != NULL) { + if (Z_TYPE_P(opt_val) == IS_ARRAY) { + /* COUNT with optional ANY: [count, "ANY"] */ + HashTable* count_ht = Z_ARRVAL_P(opt_val); + zval* count_val = zend_hash_index_find(count_ht, 0); + zval* any_val = zend_hash_index_find(count_ht, 1); + + if (count_val) { + params->options.count = zval_get_long(count_val); + } + if (any_val && Z_TYPE_P(any_val) == IS_STRING && + strcasecmp(Z_STRVAL_P(any_val), "any") == 0) { + params->options.any = 1; + } + } else { + /* Simple COUNT */ + params->options.count = zval_get_long(opt_val); + } + } + + /* SORT option (alternative to array-based) */ + if ((opt_val = zend_hash_str_find(ht, "sort", sizeof("sort") - 1)) != NULL) { + if (Z_TYPE_P(opt_val) == IS_STRING) { + params->options.sort = Z_STRVAL_P(opt_val); + params->options.sort_len = Z_STRLEN_P(opt_val); + } + } + + /* STOREDIST option (GEOSEARCHSTORE only) */ + if (is_store_variant) { + if ((opt_val = zend_hash_str_find(ht, "storedist", sizeof("storedist") - 1)) != NULL) { + params->options.store_dist = zval_is_true(opt_val); + } + } + } + + return 1; +} + +/** + * Prepare arguments for unified GEOSEARCH/GEOSEARCHSTORE commands + */ +int prepare_geo_search_unified_args(geo_search_params_t* params, + uintptr_t** args_out, + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count, + int is_store_variant) { + if (!params || !args_out || !args_len_out || !allocated_strings || !allocated_count) { + return 0; + } + + *allocated_count = 0; + + /* Calculate maximum arguments needed */ + unsigned long max_args = VALKEY_GLIDE_MAX_OPTIONS; + *args_out = (uintptr_t*) emalloc(max_args * sizeof(uintptr_t)); + *args_len_out = (unsigned long*) emalloc(max_args * sizeof(unsigned long)); + + unsigned long arg_idx = 0; + + /* Add key(s) */ + if (is_store_variant) { + /* GEOSEARCHSTORE: destination, source */ + (*args_out)[arg_idx] = (uintptr_t) params->key; + (*args_len_out)[arg_idx++] = params->key_len; + (*args_out)[arg_idx] = (uintptr_t) params->src_key; + (*args_len_out)[arg_idx++] = params->src_key_len; + } else { + /* GEOSEARCH: key */ + (*args_out)[arg_idx] = (uintptr_t) params->key; + (*args_len_out)[arg_idx++] = params->key_len; + } + + /* Add FROM parameter */ + if (params->is_from_member) { + /* FROMMEMBER */ + (*args_out)[arg_idx] = (uintptr_t) "FROMMEMBER"; + (*args_len_out)[arg_idx++] = strlen("FROMMEMBER"); + (*args_out)[arg_idx] = (uintptr_t) params->member; + (*args_len_out)[arg_idx++] = params->member_len; + } else { + /* FROMLONLAT */ + (*args_out)[arg_idx] = (uintptr_t) "FROMLONLAT"; + (*args_len_out)[arg_idx++] = strlen("FROMLONLAT"); + + /* Convert coordinates to strings */ + size_t lon_str_len, lat_str_len; + char* lon_str = double_to_string(params->longitude, &lon_str_len); + char* lat_str = double_to_string(params->latitude, &lat_str_len); + + if (!lon_str || !lat_str) { + if (lon_str) + efree(lon_str); + if (lat_str) + efree(lat_str); + efree(*args_out); + efree(*args_len_out); + return 0; + } + + (*args_out)[arg_idx] = (uintptr_t) lon_str; + (*args_len_out)[arg_idx++] = lon_str_len; + (*allocated_strings)[(*allocated_count)++] = lon_str; + + (*args_out)[arg_idx] = (uintptr_t) lat_str; + (*args_len_out)[arg_idx++] = lat_str_len; + (*allocated_strings)[(*allocated_count)++] = lat_str; + } + + /* Add BY parameter */ + if (params->is_by_radius) { + /* BYRADIUS */ + (*args_out)[arg_idx] = (uintptr_t) "BYRADIUS"; + (*args_len_out)[arg_idx++] = strlen("BYRADIUS"); + + size_t radius_str_len; + char* radius_str = double_to_string(params->radius, &radius_str_len); + if (!radius_str) { + free_allocated_strings(*allocated_strings, *allocated_count); + efree(*args_out); + efree(*args_len_out); + return 0; + } + + (*args_out)[arg_idx] = (uintptr_t) radius_str; + (*args_len_out)[arg_idx++] = radius_str_len; + (*allocated_strings)[(*allocated_count)++] = radius_str; + } else { + /* BYBOX */ + (*args_out)[arg_idx] = (uintptr_t) "BYBOX"; + (*args_len_out)[arg_idx++] = strlen("BYBOX"); + + size_t width_str_len, height_str_len; + char* width_str = double_to_string(params->width, &width_str_len); + char* height_str = double_to_string(params->height, &height_str_len); + + if (!width_str || !height_str) { + if (width_str) + efree(width_str); + if (height_str) + efree(height_str); + free_allocated_strings(*allocated_strings, *allocated_count); + efree(*args_out); + efree(*args_len_out); + return 0; + } + + (*args_out)[arg_idx] = (uintptr_t) width_str; + (*args_len_out)[arg_idx++] = width_str_len; + (*allocated_strings)[(*allocated_count)++] = width_str; + + (*args_out)[arg_idx] = (uintptr_t) height_str; + (*args_len_out)[arg_idx++] = height_str_len; + (*allocated_strings)[(*allocated_count)++] = height_str; + } + + /* Add unit */ + (*args_out)[arg_idx] = (uintptr_t) params->unit; + (*args_len_out)[arg_idx++] = params->unit_len; + + /* Add sorting option */ + if (params->options.sort && params->options.sort_len > 0) { + (*args_out)[arg_idx] = (uintptr_t) params->options.sort; + (*args_len_out)[arg_idx++] = params->options.sort_len; + } + + /* Add COUNT option */ + if (params->options.count > 0) { + (*args_out)[arg_idx] = (uintptr_t) "COUNT"; + (*args_len_out)[arg_idx++] = strlen("COUNT"); + + size_t count_str_len; + char* count_str = long_to_string(params->options.count, &count_str_len); + if (!count_str) { + free_allocated_strings(*allocated_strings, *allocated_count); + efree(*args_out); + efree(*args_len_out); + return 0; + } + + (*args_out)[arg_idx] = (uintptr_t) count_str; + (*args_len_out)[arg_idx++] = count_str_len; + (*allocated_strings)[(*allocated_count)++] = count_str; + + /* Add ANY if specified */ + if (params->options.any) { + (*args_out)[arg_idx] = (uintptr_t) "ANY"; + (*args_len_out)[arg_idx++] = strlen("ANY"); + } + } + + /* Add WITH* options (GEOSEARCH only) */ + if (!is_store_variant) { + if (params->options.with_opts.withcoord) { + (*args_out)[arg_idx] = (uintptr_t) "WITHCOORD"; + (*args_len_out)[arg_idx++] = strlen("WITHCOORD"); + } + if (params->options.with_opts.withdist) { + (*args_out)[arg_idx] = (uintptr_t) "WITHDIST"; + (*args_len_out)[arg_idx++] = strlen("WITHDIST"); + } + if (params->options.with_opts.withhash) { + (*args_out)[arg_idx] = (uintptr_t) "WITHHASH"; + (*args_len_out)[arg_idx++] = strlen("WITHHASH"); + } + } + + /* Add STOREDIST option (GEOSEARCHSTORE only) */ + if (is_store_variant && params->options.store_dist) { + (*args_out)[arg_idx] = (uintptr_t) "STOREDIST"; + (*args_len_out)[arg_idx++] = strlen("STOREDIST"); + } + + return arg_idx; +} + +/** + * Unified execution function for GEOSEARCH/GEOSEARCHSTORE + */ +int execute_geosearch_unified( + zval* object, int argc, zval* return_value, zend_class_entry* ce, int is_store_variant) { + geo_search_params_t params; + const void* glide_client = NULL; + + /* Parse parameters */ + if (!parse_geosearch_parameters(argc, object, ce, ¶ms, is_store_variant)) { + return 0; + } + + /* Get ValkeyGlide object */ + valkey_glide_object* valkey_glide = + VALKEY_GLIDE_PHP_ZVAL_GET_OBJECT(valkey_glide_object, object); + glide_client = valkey_glide->glide_client; + + /* Check if we have a valid glide client */ + if (!glide_client) { + return 0; + } + + /* Prepare command arguments */ + uintptr_t* arg_values = NULL; + unsigned long* arg_lens = NULL; + char** allocated_strings = NULL; + int allocated_count = 0; + int arg_count = 0; + + allocated_strings = (char**) emalloc(15 * sizeof(char*)); + if (!allocated_strings) { + return 0; + } + + arg_count = prepare_geo_search_unified_args( + ¶ms, &arg_values, &arg_lens, &allocated_strings, &allocated_count, is_store_variant); + + if (arg_count <= 0) { + if (allocated_strings) + efree(allocated_strings); + return 0; + } + + /* Handle batch mode */ + if (valkey_glide->is_in_batch_mode) { + enum RequestType cmd_type = is_store_variant ? GeoSearchStore : GeoSearch; + + void* result_ptr = NULL; + geo_result_processor_t processor = + is_store_variant ? process_geo_int_result_async : process_geo_search_result_async; + + if (!is_store_variant) { + /* Create search data for GEOSEARCH result processing */ + typedef struct { + int withcoord; + int withdist; + int withhash; + } search_data_t; + + search_data_t* search_data = emalloc(sizeof(search_data_t)); + search_data->withcoord = params.options.with_opts.withcoord; + search_data->withdist = params.options.with_opts.withdist; + search_data->withhash = params.options.with_opts.withhash; + result_ptr = search_data; + } + + int status = buffer_command_for_batch(valkey_glide, + cmd_type, + arg_values, + arg_lens, + arg_count, + result_ptr, + (z_result_processor_t) processor); + + /* Cleanup */ + for (int i = 0; i < allocated_count; i++) { + if (allocated_strings[i]) { + efree(allocated_strings[i]); + } + } + efree(allocated_strings); + efree(arg_values); + efree(arg_lens); + + if (status) { + ZVAL_COPY(return_value, object); + return 1; + } + return 0; + } + + /* Execute synchronously */ + enum RequestType cmd_type = is_store_variant ? GeoSearchStore : GeoSearch; + CommandResult* result = + execute_command(glide_client, cmd_type, arg_count, arg_values, arg_lens); + + /* Cleanup allocated strings */ + for (int i = 0; i < allocated_count; i++) { + if (allocated_strings[i]) { + efree(allocated_strings[i]); + } + } + efree(allocated_strings); + efree(arg_values); + efree(arg_lens); + + if (!result || result->command_error) { + if (result) + free_command_result(result); + return 0; + } + + /* Process result */ + int success = 0; + if (is_store_variant) { + success = process_geo_int_result_async(result->response, NULL, return_value); + } else { + /* Create search data for result processing */ + typedef struct { + int withcoord; + int withdist; + int withhash; + } search_data_t; + + search_data_t* search_data = emalloc(sizeof(search_data_t)); + search_data->withcoord = params.options.with_opts.withcoord; + search_data->withdist = params.options.with_opts.withdist; + search_data->withhash = params.options.with_opts.withhash; + + success = process_geo_search_result_async(result->response, search_data, return_value); + } + + free_command_result(result); + return success; +} diff --git a/valkey_glide_geo_common.h b/valkey_glide_geo_common.h index b259b3a7..48fed4c1 100644 --- a/valkey_glide_geo_common.h +++ b/valkey_glide_geo_common.h @@ -50,6 +50,35 @@ typedef struct _geo_radius_options_t { int store_dist; /* STOREDIST option (for GEOSEARCHSTORE) */ } geo_radius_options_t; +/** + * Unified parameters structure for GEOSEARCH/GEOSEARCHSTORE commands + */ +typedef struct _geo_search_params_t { + /* Basic parameters */ + const char* key; /* Source key (GEOSEARCH) or destination key (GEOSEARCHSTORE) */ + size_t key_len; + const char* src_key; /* Source key (GEOSEARCHSTORE only) */ + size_t src_key_len; + + /* Position parameters */ + int is_from_member; /* 1 if using member, 0 if using coordinates */ + const char* member; /* Member name for FROMMEMBER */ + size_t member_len; + double longitude; /* Longitude for FROMLONLAT */ + double latitude; /* Latitude for FROMLONLAT */ + + /* Shape parameters */ + int is_by_radius; /* 1 if using radius, 0 if using box */ + double radius; /* Radius for BYRADIUS */ + double width; /* Width for BYBOX */ + double height; /* Height for BYBOX */ + const char* unit; /* Unit (m, km, ft, mi) */ + size_t unit_len; + + /* Options */ + geo_radius_options_t options; +} geo_search_params_t; + /** * Common arguments structure for GEO commands */ @@ -83,6 +112,7 @@ typedef struct _geo_command_args_t { /* For GEOSEARCH/GEOSEARCHSTORE */ zval* from; /* FROMMEMBER or FROMLONLAT */ double* by_radius; /* BYRADIUS value */ + double* by_box; /* BYBOX values [width, height] */ /* For GEOSEARCHSTORE */ const char* dest; /* Destination key */ @@ -149,6 +179,21 @@ int execute_geosearchstore_command(zval* object, zval* return_value, zend_class_entry* ce); +/* New unified functions */ +int parse_geosearch_parameters(int argc, + zval* object, + zend_class_entry* ce, + geo_search_params_t* params, + int is_store_variant); +int execute_geosearch_unified( + zval* object, int argc, zval* return_value, zend_class_entry* ce, int is_store_variant); +int prepare_geo_search_unified_args(geo_search_params_t* params, + uintptr_t** args_out, + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count, + int is_store_variant); + /* Execution framework */ int execute_geo_generic_command(valkey_glide_object* valkey_glide, enum RequestType cmd_type, From 5a46bc7373863a53c41dcbe127464cdd38fe5136 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 21 Sep 2025 14:00:12 +0300 Subject: [PATCH 05/46] GEo refactor --- tests/ValkeyGlideTest.php | 110 +++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index db69d12e..b92c9bf7 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -5158,12 +5158,12 @@ public function testGeoSearchStore() private function addTestCities() { // Clear any existing data - $this->valkey_glide->del('geo_test_key'); - $this->valkey_glide->del('geo_src_key'); - $this->valkey_glide->del('geo_dst_key'); + $this->valkey_glide->del('{geo}_test_key'); + $this->valkey_glide->del('{geo}_src_key'); + $this->valkey_glide->del('{geo}_dst_key'); // Add cities with their coordinates to source key - $this->valkey_glide->geoadd('geo_src_key', + $this->valkey_glide->geoadd('{geo}_src_key', -121.837478, 39.728494, 'Chico', // Northern California -121.494400, 38.581572, 'Sacramento', // Central California -121.693583, 39.363777, 'Gridley', // Near Chico @@ -5172,7 +5172,7 @@ private function addTestCities() ); // Add cities with their coordinates - $this->valkey_glide->geoadd('geo_test_key', + $this->valkey_glide->geoadd('{geo}_test_key', -121.837478, 39.728494, 'Chico', // Northern California -121.494400, 38.581572, 'Sacramento', // Central California -121.693583, 39.363777, 'Gridley', // Near Chico @@ -5189,12 +5189,12 @@ public function testGeoSearchStoreBasicRadius() } $this->addTestCities(); // Test basic radius search and store - $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', 'Chico', 50, 'km'); + $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', 'Chico', 50, 'km'); $this->assertIsInt($count); $this->assertGTE(2, $count); // Should find at least Chico and Gridley // Verify the destination key was created and contains expected members - $members = $this->valkey_glide->zrange('geo_dst_key', 0, -1); + $members = $this->valkey_glide->zrange('{geo}_dst_key', 0, -1); $this->assertIsArray($members); $this->assertContains('Chico', $members); $this->assertContains('Gridley', $members); @@ -5207,13 +5207,13 @@ public function testGeoSearchStoreFromCoordinates() } $this->addTestCities(); // Test search and store from longitude/latitude coordinates - $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', [-121.837478, 39.728494], 50, 'km'); $this->assertIsInt($count); $this->assertGTE(2, $count); // Verify stored results - $members = $this->valkey_glide->zrange('geo_dst_key', 0, -1); + $members = $this->valkey_glide->zrange('{geo}_dst_key', 0, -1); $this->assertContains('Chico', $members); $this->assertContains('Gridley', $members); } @@ -5225,13 +5225,13 @@ public function testGeoSearchStoreByBox() } $this->addTestCities(); // Test rectangular search and store (BYBOX) - $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', 'Sacramento', [100, 100], 'km'); $this->assertIsInt($count); $this->assertGTE(1, $count); // Verify stored results - $members = $this->valkey_glide->zrange('geo_dst_key', 0, -1); + $members = $this->valkey_glide->zrange('{geo}_dst_key', 0, -1); $this->assertContains('Sacramento', $members); } @@ -5242,13 +5242,13 @@ public function testGeoSearchStoreWithCount() } $this->addTestCities(); // Test COUNT option - $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', 'Sacramento', 200, 'km', ['count' => 2]); $this->assertIsInt($count); $this->assertLTE(2, $count); // Verify stored count matches returned count - $storedCount = $this->valkey_glide->zcard('geo_dst_key'); + $storedCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals($count, $storedCount); } @@ -5259,13 +5259,13 @@ public function testGeoSearchStoreWithCountAny() } $this->addTestCities(); // Test COUNT with ANY option - $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', 'Sacramento', 200, 'km', ['count' => [3, 'ANY']]); $this->assertIsInt($count); $this->assertLTE(3, $count); // Verify stored count - $storedCount = $this->valkey_glide->zcard('geo_dst_key'); + $storedCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals($count, $storedCount); } @@ -5276,13 +5276,13 @@ public function testGeoSearchStoreWithSort() } $this->addTestCities(); // Test ASC sorting - $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', 'Sacramento', 200, 'km', ['sort' => 'ASC']); $this->assertIsInt($count); $this->assertGTE(1, $count); // Verify results are stored - $storedCount = $this->valkey_glide->zcard('geo_dst_key'); + $storedCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals($count, $storedCount); } @@ -5293,13 +5293,13 @@ public function testGeoSearchStoreWithStoreDist() } $this->addTestCities(); // Test STOREDIST option - stores distances instead of geohashes - $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', 'Chico', 50, 'km', ['storedist' => true]); $this->assertIsInt($count); $this->assertGTE(2, $count); // Verify the destination key contains distance scores - $membersWithScores = $this->valkey_glide->zrange('geo_dst_key', 0, -1, ['withscores' => true]); + $membersWithScores = $this->valkey_glide->zrange('{geo}_dst_key', 0, -1, ['withscores' => true]); $this->assertIsArray($membersWithScores); // Check that scores are distances (should be reasonable values) @@ -5318,7 +5318,7 @@ public function testGeoSearchStoreComplexQuery() } $this->addTestCities(); // Complex query: box search from coordinates with multiple options - $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', [-121.5, 38.5], // coordinates [200, 200], // box dimensions 'km', @@ -5333,11 +5333,11 @@ public function testGeoSearchStoreComplexQuery() $this->assertLTE(3, $count); // Verify stored results - $storedCount = $this->valkey_glide->zcard('geo_dst_key'); + $storedCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals($count, $storedCount); // Verify distances are stored as scores - $membersWithScores = $this->valkey_glide->zrange('geo_dst_key', 0, -1, ['withscores' => true]); + $membersWithScores = $this->valkey_glide->zrange('{geo}_dst_key', 0, -1, ['withscores' => true]); foreach ($membersWithScores as $member => $score) { $this->assertIsFloat($score); $this->assertGTE(0, $score); @@ -5354,8 +5354,8 @@ public function testGeoSearchStoreDifferentUnits() $units = ['m', 'km', 'ft', 'mi']; foreach ($units as $unit) { - $dstKey = "geo_dst_key_$unit"; - $count = $this->valkey_glide->geosearchstore($dstKey, 'geo_src_key', + $dstKey = "{geo}_dst_key_$unit"; + $count = $this->valkey_glide->geosearchstore($dstKey, '{geo}_src_key', 'Chico', 50000, $unit); $this->assertIsInt($count); $this->assertGTE(1, $count); @@ -5372,21 +5372,21 @@ public function testGeoSearchStoreOverwriteDestination() } $this->addTestCities(); // First search and store - $count1 = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + $count1 = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', 'Chico', 30, 'km'); $this->assertIsInt($count1); // Second search and store to same destination (should overwrite) - $count2 = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + $count2 = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', 'Sacramento', 100, 'km'); $this->assertIsInt($count2); // Verify the destination was overwritten - $finalCount = $this->valkey_glide->zcard('geo_dst_key'); + $finalCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals($count2, $finalCount); // Verify it contains Sacramento results, not Chico results - $members = $this->valkey_glide->zrange('geo_dst_key', 0, -1); + $members = $this->valkey_glide->zrange('{geo}_dst_key', 0, -1); $this->assertContains('Sacramento', $members); } @@ -5397,13 +5397,13 @@ public function testGeoSearchStoreEmptyResult() } $this->addTestCities(); // Search in area with no cities - $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', [0, 0], 1, 'km'); $this->assertIsInt($count); $this->assertEquals(0, $count); // Verify destination key is empty or doesn't exist - $storedCount = $this->valkey_glide->zcard('geo_dst_key'); + $storedCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals(0, $storedCount); } @@ -5414,13 +5414,13 @@ public function testGeoSearchStoreNonExistentSource() } $this->addTestCities(); // Search on non-existent source key - $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'non_existent_key', + $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}non_existent_key', 'member', 100, 'km'); $this->assertIsInt($count); $this->assertEquals(0, $count); // Verify destination key is empty - $storedCount = $this->valkey_glide->zcard('geo_dst_key'); + $storedCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals(0, $storedCount); } @@ -5431,22 +5431,22 @@ public function testGeoSearchStoreReturnValue() } $this->addTestCities(); // Test that return value matches actual stored count - $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', 'Sacramento', 200, 'km'); - $actualCount = $this->valkey_glide->zcard('geo_dst_key'); + $actualCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals($count, $actualCount); // Test with COUNT limit - $limitedCount = $this->valkey_glide->geosearchstore('geo_dst_key2', 'geo_src_key', + $limitedCount = $this->valkey_glide->geosearchstore('{geo}_dst_key2', '{geo}_src_key', 'Sacramento', 200, 'km', ['count' => 2]); - $actualLimitedCount = $this->valkey_glide->zcard('geo_dst_key2'); + $actualLimitedCount = $this->valkey_glide->zcard('{geo}_dst_key2'); $this->assertEquals($limitedCount, $actualLimitedCount); $this->assertLTE(2, $limitedCount); // Clean up - $this->valkey_glide->del('geo_dst_key2'); + $this->valkey_glide->del('{geo}_dst_key2'); } public function testGeoSearchStorePreservesGeoData() @@ -5456,17 +5456,17 @@ public function testGeoSearchStorePreservesGeoData() } $this->addTestCities(); // Store without STOREDIST (should preserve geo data) - $count = $this->valkey_glide->geosearchstore('geo_dst_key', 'geo_src_key', + $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', 'Chico', 50, 'km'); $this->assertGTE(2, $count); // Verify we can perform geo operations on the stored data - $distance = $this->valkey_glide->geodist('geo_dst_key', 'Chico', 'Gridley', 'km'); + $distance = $this->valkey_glide->geodist('{geo}_dst_key', 'Chico', 'Gridley', 'km'); $this->assertIsFloat($distance); $this->assertGTE(0, $distance); // Verify geopos works on stored data - $positions = $this->valkey_glide->geopos('geo_dst_key', 'Chico'); + $positions = $this->valkey_glide->geopos('{geo}_dst_key', 'Chico'); $this->assertIsArray($positions); $this->assertCount(1, $positions); $this->assertIsArray($positions[0]); @@ -5480,7 +5480,7 @@ public function testGeoSearchBasicRadius() } $this->addTestCities(); // Test basic radius search from member - $result = $this->valkey_glide->geosearch('geo_test_key', 'Chico', 50, 'km'); + $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Chico', 50, 'km'); $this->assertIsArray($result); $this->assertContains('Chico', $result); $this->assertContains('Gridley', $result); @@ -5496,7 +5496,7 @@ public function testGeoSearchFromCoordinates() } $this->addTestCities(); // Test search from longitude/latitude coordinates - $result = $this->valkey_glide->geosearch('geo_test_key', [-121.837478, 39.728494], 50, 'km'); + $result = $this->valkey_glide->geosearch('{geo}_test_key', [-121.837478, 39.728494], 50, 'km'); $this->assertIsArray($result); $this->assertContains('Chico', $result); $this->assertContains('Gridley', $result); @@ -5509,7 +5509,7 @@ public function testGeoSearchByBox() } $this->addTestCities(); // Test rectangular search (BYBOX) - $result = $this->valkey_glide->geosearch('geo_test_key', 'Sacramento', [150, 150], 'km'); + $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Sacramento', [150, 150], 'km'); $this->assertIsArray($result); $this->assertContains('Sacramento', $result); @@ -5523,7 +5523,7 @@ public function testGeoSearchWithCoord() $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); } $this->addTestCities(); - $result = $this->valkey_glide->geosearch('geo_test_key', 'Chico', 50, 'km', ['withcoord']); + $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Chico', 50, 'km', ['withcoord']); $this->assertIsArray($result); foreach ($result as $city => $data) { @@ -5543,7 +5543,7 @@ public function testGeoSearchWithDist() $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); } $this->addTestCities(); - $result = $this->valkey_glide->geosearch('geo_test_key', 'Chico', 50, 'km', ['withdist']); + $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Chico', 50, 'km', ['withdist']); $this->assertIsArray($result); foreach ($result as $city => $data) { @@ -5561,7 +5561,7 @@ public function testGeoSearchWithHash() $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); } $this->addTestCities(); - $result = $this->valkey_glide->geosearch('geo_test_key', 'Chico', 50, 'km', ['withhash']); + $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Chico', 50, 'km', ['withhash']); $this->assertIsArray($result); foreach ($result as $city => $data) { @@ -5578,7 +5578,7 @@ public function testGeoSearchWithAllOptions() $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); } $this->addTestCities(); - $result = $this->valkey_glide->geosearch('geo_test_key', 'Chico', 50, 'km', + $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Chico', 50, 'km', ['withdist', 'withhash', 'withcoord']); $this->assertIsArray($result); @@ -5600,7 +5600,7 @@ public function testGeoSearchWithCount() } $this->addTestCities(); // Test COUNT option - $result = $this->valkey_glide->geosearch('geo_test_key', 'Sacramento', 200, 'km', + $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Sacramento', 200, 'km', ['count' => 2]); $this->assertIsArray($result); $this->assertLTE(2, count($result)); @@ -5613,7 +5613,7 @@ public function testGeoSearchWithCountAny() } $this->addTestCities(); // Test COUNT with ANY option - $result = $this->valkey_glide->geosearch('geo_test_key', 'Sacramento', 200, 'km', + $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Sacramento', 200, 'km', ['count' => [2, 'ANY']]); $this->assertIsArray($result); $this->assertLTE(2, count($result)); @@ -5626,7 +5626,7 @@ public function testGeoSearchWithSortAsc() } $this->addTestCities(); // Test ASC sorting with distances - $result = $this->valkey_glide->geosearch('geo_test_key', 'Sacramento', 200, 'km', + $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Sacramento', 200, 'km', ['withdist', 'asc']); $this->assertIsArray($result); @@ -5649,7 +5649,7 @@ public function testGeoSearchWithSortDesc() $this->addTestCities(); // Test DESC sorting with distances - $result = $this->valkey_glide->geosearch('geo_test_key', 'Sacramento', 200, 'km', + $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Sacramento', 200, 'km', ['withdist', 'desc']); $this->assertIsArray($result); @@ -5671,7 +5671,7 @@ public function testGeoSearchAlternativeSortSyntax() } $this->addTestCities(); // Test alternative sort syntax using 'sort' key - $result = $this->valkey_glide->geosearch('geo_test_key', 'Sacramento', 200, 'km', + $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Sacramento', 200, 'km', ['withdist', 'sort' => 'ASC']); $this->assertIsArray($result); @@ -5696,7 +5696,7 @@ public function testGeoSearchDifferentUnits() $units = ['m', 'km', 'ft', 'mi']; foreach ($units as $unit) { - $result = $this->valkey_glide->geosearch('geo_test_key', 'Chico', 50000, $unit); + $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Chico', 50000, $unit); $this->assertIsArray($result); $this->assertTrue(count($result) > 0); } @@ -5709,7 +5709,7 @@ public function testGeoSearchComplexQuery() } $this->addTestCities(); // Complex query: box search from coordinates with all options - $result = $this->valkey_glide->geosearch('geo_test_key', + $result = $this->valkey_glide->geosearch('{geo}_test_key', [-121.5, 38.5], // coordinates [200, 200], // box dimensions 'km', @@ -5739,7 +5739,7 @@ public function testGeoSearchEmptyResult() } $this->addTestCities(); // Search in area with no cities - $result = $this->valkey_glide->geosearch('geo_test_key', [0, 0], 1, 'km'); + $result = $this->valkey_glide->geosearch('{geo}_test_key', [0, 0], 1, 'km'); $this->assertIsArray($result); $this->assertEquals(0, count($result)); } From d7761ca0fdc839767e56ec7efc372421053fb07b Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 21 Sep 2025 17:26:14 +0300 Subject: [PATCH 06/46] updates --- command_response.c | 76 ------- command_response.h | 8 - tests/ValkeyGlideBatchTest.php | 2 +- tests/ValkeyGlideClusterTest.php | 196 ++++++++--------- tests/ValkeyGlideTest.php | 12 ++ valkey_glide_commands.c | 18 -- valkey_glide_commands_3.c | 56 ++--- valkey_glide_geo_common.c | 349 +------------------------------ valkey_glide_str_commands.c | 11 - 9 files changed, 118 insertions(+), 610 deletions(-) diff --git a/command_response.c b/command_response.c index 366638a1..9aeb1093 100644 --- a/command_response.c +++ b/command_response.c @@ -133,39 +133,6 @@ int parse_cluster_route(zval* route_zval, cluster_route_t* route) { return 1; } } - - return 0; /* Invalid type-based routing */ - } - - /* Try direct host/port keys */ - host_zv = zend_hash_str_find(route_ht, "host", sizeof("host") - 1); - port_zv = zend_hash_str_find(route_ht, "port", sizeof("port") - 1); - - if (!host_zv || !port_zv) { - /* Try numeric keys (indexed array approach) */ - host_zv = zend_hash_index_find(route_ht, 0); - port_zv = zend_hash_index_find(route_ht, 1); - } - - if (host_zv && port_zv && Z_TYPE_P(host_zv) == IS_STRING) { - /* Set route type to host:port */ - route->type = ROUTE_TYPE_HOST_PORT; - - /* Get host from array */ - route->data.host_port_route.host = Z_STRVAL_P(host_zv); - - /* Get port from array */ - if (Z_TYPE_P(port_zv) == IS_LONG) { - route->data.host_port_route.port = Z_LVAL_P(port_zv); - } else { - zval temp; - ZVAL_COPY(&temp, port_zv); - convert_to_long(&temp); - route->data.host_port_route.port = Z_LVAL(temp); - zval_dtor(&temp); - } - - return 1; } } @@ -271,12 +238,6 @@ CommandResult* execute_command_with_route(const void* glide_client, const uintptr_t* args, const unsigned long* args_len, zval* arg_route) { - /* Check if client is valid */ - if (!glide_client) { - printf("Error: glide_client is NULL\n"); - return NULL; - } - /* Validate route parameter */ if (!arg_route) { printf("Error: arg_route is NULL\n"); @@ -698,43 +659,6 @@ int command_response_to_zval(CommandResponse* response, } } -/* Handle a set response */ -int handle_set_response(CommandResult* result, zval* output) { - /* Check if the command was successful */ - if (!result) { - return -1; - } - - /* Check if there was an error */ - if (result->command_error) { - printf("%s:%d - Error executing command: %s\n", - __FILE__, - __LINE__, - result->command_error->command_error_message); - free_command_result(result); - return -1; - } - - /* Process the result */ - int ret_val = -1; - if (result->response) { - if (result->response->response_type == Null) { - ZVAL_NULL(output); - ret_val = 0; - } else if (result->response->response_type == Sets) { - ret_val = command_response_to_zval( - result->response, output, COMMAND_RESPONSE_NOT_ASSOSIATIVE, false); - } else { - ret_val = -1; - } - } - - /* Free the result */ - free_command_result(result); - - return ret_val; -} - /* Convert a long value to a string */ char* long_to_string(long value, size_t* len) { char buffer[32]; diff --git a/command_response.h b/command_response.h index 7177ee42..8ecdc005 100644 --- a/command_response.h +++ b/command_response.h @@ -69,14 +69,6 @@ int handle_string_response(CommandResult* result, char** output, size_t* output_ */ int handle_map_response(CommandResult* result, zval* output); -/* - * Handle a set response - * Returns 1 on success, 0 if null, -1 on error - * The output parameter is set to a PHP array (with unique values) - * This function frees the CommandResult - */ -int handle_set_response(CommandResult* result, zval* output); - /* * Helper function to convert a CommandResponse to a PHP value * Returns 1 on success, 0 if null, -1 on error diff --git a/tests/ValkeyGlideBatchTest.php b/tests/ValkeyGlideBatchTest.php index 14bfe7c5..3deb007e 100644 --- a/tests/ValkeyGlideBatchTest.php +++ b/tests/ValkeyGlideBatchTest.php @@ -2655,7 +2655,7 @@ public function testFailedTransactions() $this->assertFalse($ret); // failed because another client changed our watched key between WATCH and EXEC. // watch and unwatch - $this->valkey_glide->watch('x'); + $this->valkey_glide->watch(['x']); $r->incr('x'); // other instance $this->valkey_glide->unwatch(); // cancel transaction watch diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index fcdceb50..e12fc7f2 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -154,8 +154,13 @@ public function testConfig() } public function testFlushDB() { - $this->markTestSkipped(); + $key = "key:0"; + $this->assertTrue($this->valkey_glide->flushdb($key)); + $this->assertTrue($this->valkey_glide->flushdb($key, null)); + $this->assertTrue($this->valkey_glide->flushdb($key, false)); + $this->assertTrue($this->valkey_glide->flushdb($key, true)); } + public function testFunction() { $this->markTestSkipped(); @@ -210,13 +215,7 @@ public function testPing() for ($i = 0; $i < 20; $i++) { $this->assertTrue($this->valkey_glide->ping(['type' => 'primarySlotKey', 'key' => "key:$i"])); $this->assertEquals('BEEP', $this->valkey_glide->ping(['type' => 'primarySlotKey', 'key' => "key:$i"], 'BEEP')); - } - return; // TODO: multi - /* Make sure both variations work in MULTI mode */ - $this->valkey_glide->multi(); - $this->valkey_glide->ping(['type' => 'primarySlotKey', 'key' => '{ping-test}']); - $this->valkey_glide->ping(['type' => 'primarySlotKey', 'key' => '{ping-test}'], 'BEEP'); - $this->assertEquals([true, 'BEEP'], $this->valkey_glide->exec()); + } } public function testRandomKey() @@ -242,19 +241,16 @@ public function testEcho() } public function testSortPrefix() - { - $this->markTestSkipped(); - $this->valkey_glide->setOption(ValkeyGlide::OPT_PREFIX, 'some-prefix:'); - $this->valkey_glide->del('some-item'); - $this->valkey_glide->sadd('some-item', 1); - $this->valkey_glide->sadd('some-item', 2); - $this->valkey_glide->sadd('some-item', 3); - - $this->assertEquals(['1', '2', '3'], $this->valkey_glide->sort('some-item')); + { + $this->valkey_glide->del('some-prefix:some-item'); + $this->valkey_glide->sadd('some-prefix:some-item', 1); + $this->valkey_glide->sadd('some-prefix:some-item', 2); + $this->valkey_glide->sadd('some-prefix:some-item', 3); + $this->assertEquals(['1', '2', '3'], $this->valkey_glide->sort('some-prefix:some-item')); // Kill our set/prefix - $this->valkey_glide->del('some-item'); - $this->valkey_glide->setOption(ValkeyGlide::OPT_PREFIX, ''); + $this->valkey_glide->del('some-prefix:some-item'); + } public function testDBSize() @@ -272,6 +268,7 @@ public function testFlushAll() for ($i = 0; $i < 10; $i++) { $key = "key:$i"; + $this->assertTrue($this->valkey_glide->flushAll($key, true)); $this->assertTrue($this->valkey_glide->flushAll($key)); $this->assertEquals(0, $this->valkey_glide->dbsize($key)); $this->valkey_glide->set($key, "val:$i"); @@ -288,37 +285,91 @@ public function testInfo() "role" ]; - // Test individual node INFO calls + // Test 1: primarySlotKey routing (array format) for ($i = 0; $i < 3; $i++) { - $info = $this->valkey_glide->info(['type' => 'primarySlotKey', 'key' => $i]); + $info = $this->valkey_glide->info(['type' => 'primarySlotKey', 'key' => "test-key-$i"]); + $this->assertIsArray($info); foreach ($fields as $field) { $this->assertArrayKey($info, $field); } } - // Test AllNodes INFO response + // Test 2: randomNode routing (string format) + $randomNodeInfo = $this->valkey_glide->info("randomNode"); + $this->assertIsArray($randomNodeInfo); + foreach ($fields as $field) { + $this->assertArrayKey($randomNodeInfo, $field); + } + + // Test 3: randomNode with specific section + $randomNodeServerInfo = $this->valkey_glide->info("randomNode", "server"); + $this->assertIsArray($randomNodeServerInfo); + $this->assertArrayKey($randomNodeServerInfo, "redis_version"); + + // Test 4: allPrimaries routing + $allPrimariesInfo = $this->valkey_glide->info("allPrimaries"); + $this->assertIsArray($allPrimariesInfo); + $this->assertGT(0, count($allPrimariesInfo), "allPrimaries should return data from multiple nodes"); + + // Test 5: allPrimaries with specific section + $allPrimariesMemoryInfo = $this->valkey_glide->info("allPrimaries", "memory"); + $this->assertIsArray($allPrimariesMemoryInfo); + $this->assertEquals(6, count($allPrimariesMemoryInfo), "Should have 12 entries (6 nodes * 2 entries each)"); + + // Test 6: allNodes routing $allNodesInfo = $this->valkey_glide->info("allNodes", "cpu"); - // Should return an array - $this->assertIsArray($allNodesInfo, 12); - // Should have 6 entries (one per node) - $this->assertEquals(12, count($allNodesInfo), "Should have 6 node entries"); + $this->assertIsArray($allNodesInfo); + $this->assertEquals(12, count($allNodesInfo), "Should have 12 entries (6 nodes * 2 entries each)"); $nodesSeen = []; - // Test each node entry foreach ($allNodesInfo as $index => $nodeInfo) { if ($index % 2 == 0) { $this->assertIsInt($nodeInfo['127.0.0.1'], "Port field should be an integer"); $nodePort = $nodeInfo['127.0.0.1']; - $this->assertFalse(array_key_exists($nodePort, $nodesSeen)); $this->assertIsArray($nodeInfo, 1); $nodesSeen[$nodePort] = true; } else { - // Should contain used_cpu_sys field (since we requested it) + // Should contain used_cpu_sys field (since we requested cpu section) $this->assertArrayKey($nodeInfo, 'used_cpu_sys'); } } + + // Test 7: Simple key string routing (slot-based routing) + $keyBasedInfo = $this->valkey_glide->info("simple-test-key"); + $this->assertIsArray($keyBasedInfo); + foreach ($fields as $field) { + $this->assertArrayKey($keyBasedInfo, $field); + } + + // Test 8: Simple key string routing with section + $keyBasedServerInfo = $this->valkey_glide->info("simple-test-key", "server"); + $this->assertIsArray($keyBasedServerInfo); + $this->assertArrayKey($keyBasedServerInfo, "redis_version"); + + // Test 9: routeByAddress routing (specific node) + $routeByAddressInfo = $this->valkey_glide->info(['type' => 'routeByAddress', 'host' => '127.0.0.1', 'port' => 7001]); + $this->assertIsArray($routeByAddressInfo); + foreach ($fields as $field) { + $this->assertArrayKey($routeByAddressInfo, $field); + } + + // Test 10: routeByAddress with specific section + $routeByAddressMemoryInfo = $this->valkey_glide->info(['type' => 'routeByAddress', 'host' => '127.0.0.1', 'port' => 7001], "memory"); + $this->assertIsArray($routeByAddressMemoryInfo); + $this->assertArrayKey($routeByAddressMemoryInfo, "used_memory"); + + // Test 11: Multiple sections with different routing types + $multiSectionInfo = $this->valkey_glide->info("randomNode", "server", "memory"); + $this->assertIsArray($multiSectionInfo); + $this->assertArrayKey($multiSectionInfo, "redis_version"); + $this->assertArrayKey($multiSectionInfo, "used_memory"); + + // Test 12: All sections (no section parameter) + $allSectionsInfo = $this->valkey_glide->info("randomNode"); + $this->assertIsArray($allSectionsInfo); + $this->assertGT(10, count($allSectionsInfo), "All sections should return many fields"); } public function testClient() @@ -519,7 +570,6 @@ public function testInfoCommandStats() * failed, because the commands could be coming from multiple nodes */ public function testFailedTransactions() { - $this->markTestSkipped(); $this->valkey_glide->set('x', 42); // failed transaction @@ -530,9 +580,9 @@ public function testFailedTransactions() // This transaction should fail because the other client changed 'x' $ret = $this->valkey_glide->multi()->get('x')->exec(); - $this->assertEquals([false], $ret); + $this->assertEquals(false, $ret); // watch and unwatch - $this->valkey_glide->watch('x'); + $this->valkey_glide->watch(['x']); $r->incr('x'); // other instance $this->valkey_glide->unwatch(); // cancel transaction watch @@ -856,84 +906,4 @@ public function testReplyLiteral() } - - /* Test that we are able to use the slot cache without issues */ - public function testSlotCache() - { - ini_set('redis.clusters.cache_slots', 1); - - $pong = 0; - for ($i = 0; $i < 10; $i++) { - $new_client = $this->newInstance(); - $pong += $new_client->ping("key:$i"); - } - - $this->assertEquals($pong, $i); - - ini_set('redis.clusters.cache_slots', 0); - } - - /* Regression test for connection pool liveness checks */ - public function testConnectionPool() - { - $prev_value = ini_get('redis.pconnect.pooling_enabled'); - ini_set('redis.pconnect.pooling_enabled', 1); - - $pong = 0; - for ($i = 0; $i < 10; $i++) { - $new_client = $this->newInstance(); - $pong += $new_client->ping("key:$i"); - } - - $this->assertEquals($pong, $i); - ini_set('redis.pconnect.pooling_enabled', $prev_value); - } - - protected function sessionPrefix(): string - { - return 'VALKEY_GLIDE_PHP_CLUSTER_SESSION:'; - } - - protected function sessionSaveHandler(): string - { - return 'rediscluster'; - } - - /** - * @inheritdoc - */ - protected function sessionSavePath(): string - { - return implode('&', array_map(function ($host) { - return 'seed[]=' . $host; - }, self::$seeds)) . '&' . $this->getAuthFragment(); - } - - /* Test correct handling of null multibulk replies */ - public function testNullArray() - { - $this->markTestSkipped(); - - $key = "key:arr"; - $this->valkey_glide->del($key); - - foreach ([false => [], true => null] as $opt => $test) { - $this->valkey_glide->setOption(ValkeyGlide::OPT_NULL_MULTIBULK_AS_NULL, $opt); - - $r = $this->valkey_glide->rawCommand($key, "BLPOP", $key, .05); - $this->assertEquals($test, $r); - - $this->valkey_glide->multi(); - $this->valkey_glide->rawCommand($key, "BLPOP", $key, .05); - $r = $this->valkey_glide->exec(); - $this->assertEquals([$test], $r); - } - - $this->valkey_glide->setOption(ValkeyGlide::OPT_NULL_MULTIBULK_AS_NULL, false); - } - - protected function execWaitAOF() - { - return $this->valkey_glide->waitaof(uniqid(), 0, 0, 0); - } -} +} diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index b92c9bf7..24680cc5 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -1096,6 +1096,18 @@ public function testTouch() $idle1 = $this->valkey_glide->object('idletime', '{idle}1'); $idle2 = $this->valkey_glide->object('idletime', '{idle}2'); + + + /* We're not testing if idle is 0 because CPU scheduling on GitHub CI + * potatoes can cause that to erroneously fail. */ + $this->assertLT(2, $idle1); + $this->assertLT(2, $idle2); + + $this->assertEquals(2, $this->valkey_glide->touch(['{idle}1', '{idle}2', '{idle}notakey'])); + + $idle1 = $this->valkey_glide->object('idletime', '{idle}1'); + $idle2 = $this->valkey_glide->object('idletime', '{idle}2'); + /* We're not testing if idle is 0 because CPU scheduling on GitHub CI * potatoes can cause that to erroneously fail. */ diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index 86691cfb..9f632dd1 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -382,12 +382,6 @@ int execute_unwatch_command(zval* object, int argc, zval* return_value, zend_cla args.cmd_type = UnWatch; if (execute_core_command(valkey_glide, &args, NULL, process_core_bool_result, return_value)) { - if (valkey_glide->is_in_batch_mode) { - /* In batch mode, return $this for method chaining */ - ZVAL_COPY(return_value, object); - return 1; - } - return 1; } else { return 0; @@ -428,18 +422,6 @@ static int process_object_command_result(CommandResponse* response, ZVAL_FALSE(return_value); return 0; } - } else if (strncasecmp(subcommand, "HELP", subcommand_len) == 0) { - /* HELP returns an array of strings */ - if (response->response_type == Array) { - if (command_response_to_zval( - response, return_value, COMMAND_RESPONSE_NOT_ASSOSIATIVE, false) == 1) { - return 1; - } else { - return -1; - } - } else { - return -1; - } } else { /* Unsupported subcommand */ return -1; diff --git a/valkey_glide_commands_3.c b/valkey_glide_commands_3.c index 69c41b71..bcd70f47 100644 --- a/valkey_glide_commands_3.c +++ b/valkey_glide_commands_3.c @@ -1722,55 +1722,39 @@ int execute_client_command(zval* object, int argc, zval* return_value, zend_clas if (command_type == InvalidRequest) { return 0; /* Unknown command */ } - - /* Convert arguments to uint8_t** format for batch processing */ - uintptr_t* batch_args = NULL; - unsigned long* arg_lengths = NULL; - + uintptr_t* cmd_args = NULL; + unsigned long* args_len = NULL; + char** allocated_strings = NULL; + int allocated_count = 0; if (arg_count > 1) { - batch_args = (uintptr_t*) emalloc((arg_count - 1) * sizeof(uintptr_t)); - arg_lengths = emalloc((arg_count - 1) * sizeof(unsigned long)); - - /* Convert each argument to string and store */ - for (int i = 1; i < arg_count; i++) { - zval* arg = &z_args[i]; - - if (Z_TYPE_P(arg) == IS_STRING) { - /* Already a string, use directly */ - batch_args[i - 1] = (uintptr_t) Z_STRVAL_P(arg); - - arg_lengths[i - 1] = Z_STRLEN_P(arg); - } else { - /* Convert to string */ - zval temp; - ZVAL_COPY(&temp, arg); - convert_to_string(&temp); - - batch_args[i - 1] = (uintptr_t) Z_STRVAL(temp); - arg_lengths[i - 1] = Z_STRLEN(temp); - - /* Note: We're not freeing temp here as batch_args points to its string data - */ - } + if (!convert_zval_args_to_strings(&z_args[1], + arg_count - 1, + &cmd_args, + &args_len, + &allocated_strings, + &allocated_count)) { + return 0; } } + + enum RequestType* output = emalloc(sizeof(enum RequestType)); *output = command_type; /* Buffer the command for batch execution */ int buffer_result = buffer_command_for_batch(valkey_glide, command_type, - batch_args, - arg_lengths, + cmd_args, + args_len, arg_count - 1, /* number of args */ output, /* result_ptr */ command_response_to_zval_wrapper); /* Free the argument arrays */ - if (batch_args) - efree(batch_args); - if (arg_lengths) - efree(arg_lengths); - + if (cmd_args) + efree(cmd_args); + if (args_len) + efree(args_len); + cleanup_allocated_strings(allocated_strings, allocated_count); if (buffer_result) { /* In batch mode, return $this for method chaining */ ZVAL_COPY(return_value, object); diff --git a/valkey_glide_geo_common.c b/valkey_glide_geo_common.c index 018bec27..96eb1ed6 100644 --- a/valkey_glide_geo_common.c +++ b/valkey_glide_geo_common.c @@ -172,279 +172,6 @@ int prepare_geo_add_args(geo_command_args_t* args, } -/** - * Prepare GEOSEARCH command arguments - */ -int prepare_geo_search_args(geo_command_args_t* args, - uintptr_t** args_out, - unsigned long** args_len_out, - char*** allocated_strings, - int* allocated_count) { - /* Check if client is valid */ - if (!args || !args->key || !args->from || !args->by_radius || !args->unit || !args_out || - !args_len_out || !allocated_strings || !allocated_count) { - return 0; - } - - *allocated_count = 0; - - /* Calculate the maximum arguments we might need */ - unsigned long max_args = 15; /* Conservative estimate */ - *args_out = (uintptr_t*) emalloc(max_args * sizeof(uintptr_t)); - *args_len_out = (unsigned long*) emalloc(max_args * sizeof(unsigned long)); - - /* Start building command arguments */ - unsigned long arg_idx = 0; - - /* First argument: key */ - (*args_out)[arg_idx] = (uintptr_t) args->key; - (*args_len_out)[arg_idx++] = args->key_len; - - /* Handle FROM parameter - could be member name or coordinates */ - if (Z_TYPE_P(args->from) == IS_STRING) { - /* FROMMEMBER */ - (*args_out)[arg_idx] = (uintptr_t) "FROMMEMBER"; - (*args_len_out)[arg_idx++] = strlen("FROMMEMBER"); - - (*args_out)[arg_idx] = (uintptr_t) Z_STRVAL_P(args->from); - (*args_len_out)[arg_idx++] = Z_STRLEN_P(args->from); - } else if (Z_TYPE_P(args->from) == IS_ARRAY) { - /* FROMLONLAT */ - zval *lon, *lat; - lon = zend_hash_index_find(Z_ARRVAL_P(args->from), 0); - lat = zend_hash_index_find(Z_ARRVAL_P(args->from), 1); - - if (lon && lat) { - (*args_out)[arg_idx] = (uintptr_t) "FROMLONLAT"; - (*args_len_out)[arg_idx++] = strlen("FROMLONLAT"); - - /* Convert longitude and latitude to strings */ - size_t lon_str_len, lat_str_len; - char* lon_str = double_to_string(zval_get_double(lon), &lon_str_len); - - (*args_out)[arg_idx] = (uintptr_t) lon_str; - (*args_len_out)[arg_idx++] = lon_str_len; - (*allocated_strings)[(*allocated_count)++] = lon_str; - - char* lat_str = double_to_string(zval_get_double(lat), &lat_str_len); - if (!lat_str) { - free_allocated_strings(*allocated_strings, *allocated_count); - efree(*args_out); - efree(*args_len_out); - return 0; - } - (*args_out)[arg_idx] = (uintptr_t) lat_str; - (*args_len_out)[arg_idx++] = lat_str_len; - (*allocated_strings)[(*allocated_count)++] = lat_str; - } - } - - /* Handle BY parameter */ - if (args->by_radius != NULL) { - /* BYRADIUS */ - (*args_out)[arg_idx] = (uintptr_t) "BYRADIUS"; - (*args_len_out)[arg_idx++] = strlen("BYRADIUS"); - - /* Convert radius to string */ - size_t radius_str_len; - char* radius_str = double_to_string(*args->by_radius, &radius_str_len); - (*args_out)[arg_idx] = (uintptr_t) radius_str; - (*args_len_out)[arg_idx++] = radius_str_len; - (*allocated_strings)[(*allocated_count)++] = radius_str; - - (*args_out)[arg_idx] = (uintptr_t) args->unit; - (*args_len_out)[arg_idx++] = args->unit_len; - } - - /* Add WITH* options if enabled */ - if (args->radius_opts.with_opts.withcoord) { - (*args_out)[arg_idx] = (uintptr_t) "WITHCOORD"; - (*args_len_out)[arg_idx++] = strlen("WITHCOORD"); - } - - if (args->radius_opts.with_opts.withdist) { - (*args_out)[arg_idx] = (uintptr_t) "WITHDIST"; - (*args_len_out)[arg_idx++] = strlen("WITHDIST"); - } - - if (args->radius_opts.with_opts.withhash) { - (*args_out)[arg_idx] = (uintptr_t) "WITHHASH"; - (*args_len_out)[arg_idx++] = strlen("WITHHASH"); - } - - /* Add COUNT option if set */ - if (args->radius_opts.count > 0) { - (*args_out)[arg_idx] = (uintptr_t) "COUNT"; - (*args_len_out)[arg_idx++] = strlen("COUNT"); - - /* Convert count to string */ - size_t count_str_len; - char* count_str = long_to_string(args->radius_opts.count, &count_str_len); - if (!count_str) { - free_allocated_strings(*allocated_strings, *allocated_count); - efree(*args_out); - efree(*args_len_out); - return 0; - } - - (*args_out)[arg_idx] = (uintptr_t) count_str; - (*args_len_out)[arg_idx++] = count_str_len; - (*allocated_strings)[(*allocated_count)++] = count_str; - - /* Add ANY if specified */ - if (args->radius_opts.any) { - (*args_out)[arg_idx] = (uintptr_t) "ANY"; - (*args_len_out)[arg_idx++] = strlen("ANY"); - } - } - - /* Add sorting option if specified */ - if (args->radius_opts.sort && args->radius_opts.sort_len > 0) { - (*args_out)[arg_idx] = (uintptr_t) args->radius_opts.sort; - (*args_len_out)[arg_idx++] = args->radius_opts.sort_len; - } - - return arg_idx; -} - -/** - * Prepare GEOSEARCHSTORE command arguments - */ -int prepare_geo_search_store_args(geo_command_args_t* args, - uintptr_t** args_out, - unsigned long** args_len_out, - char*** allocated_strings, - int* allocated_count) { - /* Check if client is valid */ - if (!args || !args->dest || !args->src || !args->from || !args->by_radius || !args->unit || - !args_out || !args_len_out || !allocated_strings || !allocated_count) { - return 0; - } - - *allocated_count = 0; - - /* Calculate the maximum arguments we might need */ - unsigned long max_args = 16; /* Conservative estimate */ - *args_out = (uintptr_t*) emalloc(max_args * sizeof(uintptr_t)); - *args_len_out = (unsigned long*) emalloc(max_args * sizeof(unsigned long)); - - /* Start building command arguments */ - unsigned long arg_idx = 0; - - /* First two arguments: destination and source keys */ - (*args_out)[arg_idx] = (uintptr_t) args->dest; - (*args_len_out)[arg_idx++] = args->dest_len; - - (*args_out)[arg_idx] = (uintptr_t) args->src; - (*args_len_out)[arg_idx++] = args->src_len; - - /* Handle FROM parameter - could be member name or coordinates */ - if (Z_TYPE_P(args->from) == IS_STRING) { - /* FROMMEMBER */ - (*args_out)[arg_idx] = (uintptr_t) "FROMMEMBER"; - (*args_len_out)[arg_idx++] = strlen("FROMMEMBER"); - - (*args_out)[arg_idx] = (uintptr_t) Z_STRVAL_P(args->from); - (*args_len_out)[arg_idx++] = Z_STRLEN_P(args->from); - } else if (Z_TYPE_P(args->from) == IS_ARRAY) { - /* FROMLONLAT */ - zval *lon, *lat; - lon = zend_hash_index_find(Z_ARRVAL_P(args->from), 0); - lat = zend_hash_index_find(Z_ARRVAL_P(args->from), 1); - - if (lon && lat) { - (*args_out)[arg_idx] = (uintptr_t) "FROMLONLAT"; - (*args_len_out)[arg_idx++] = strlen("FROMLONLAT"); - - /* Convert longitude and latitude to strings */ - size_t lon_str_len, lat_str_len; - char* lon_str = double_to_string(zval_get_double(lon), &lon_str_len); - if (!lon_str) { - efree(*args_out); - efree(*args_len_out); - return 0; - } - (*args_out)[arg_idx] = (uintptr_t) lon_str; - (*args_len_out)[arg_idx++] = lon_str_len; - (*allocated_strings)[(*allocated_count)++] = lon_str; - - char* lat_str = double_to_string(zval_get_double(lat), &lat_str_len); - if (!lat_str) { - free_allocated_strings(*allocated_strings, *allocated_count); - efree(*args_out); - efree(*args_len_out); - return 0; - } - (*args_out)[arg_idx] = (uintptr_t) lat_str; - (*args_len_out)[arg_idx++] = lat_str_len; - (*allocated_strings)[(*allocated_count)++] = lat_str; - } - } - - /* Handle BY parameter */ - if (args->by_radius != NULL) { - /* BYRADIUS */ - (*args_out)[arg_idx] = (uintptr_t) "BYRADIUS"; - (*args_len_out)[arg_idx++] = strlen("BYRADIUS"); - - /* Convert radius to string */ - size_t radius_str_len; - char* radius_str = double_to_string(*args->by_radius, &radius_str_len); - if (!radius_str) { - free_allocated_strings(*allocated_strings, *allocated_count); - efree(*args_out); - efree(*args_len_out); - return 0; - } - (*args_out)[arg_idx] = (uintptr_t) radius_str; - (*args_len_out)[arg_idx++] = radius_str_len; - (*allocated_strings)[(*allocated_count)++] = radius_str; - - (*args_out)[arg_idx] = (uintptr_t) args->unit; - (*args_len_out)[arg_idx++] = args->unit_len; - } - - /* Add COUNT option if set */ - if (args->radius_opts.count > 0) { - (*args_out)[arg_idx] = (uintptr_t) "COUNT"; - (*args_len_out)[arg_idx++] = strlen("COUNT"); - - /* Convert count to string */ - size_t count_str_len; - char* count_str = long_to_string(args->radius_opts.count, &count_str_len); - if (!count_str) { - free_allocated_strings(*allocated_strings, *allocated_count); - efree(*args_out); - efree(*args_len_out); - return 0; - } - - (*args_out)[arg_idx] = (uintptr_t) count_str; - (*args_len_out)[arg_idx++] = count_str_len; - (*allocated_strings)[(*allocated_count)++] = count_str; - - /* Add ANY if specified */ - if (args->radius_opts.any) { - (*args_out)[arg_idx] = (uintptr_t) "ANY"; - (*args_len_out)[arg_idx++] = strlen("ANY"); - } - } - - /* Add sorting option if specified */ - if (args->radius_opts.sort && args->radius_opts.sort_len > 0) { - (*args_out)[arg_idx] = (uintptr_t) args->radius_opts.sort; - (*args_len_out)[arg_idx++] = args->radius_opts.sort_len; - } - - /* Add STOREDIST if specified */ - if (args->radius_opts.store_dist) { - (*args_out)[arg_idx] = (uintptr_t) "STOREDIST"; - (*args_len_out)[arg_idx++] = strlen("STOREDIST"); - } - - return arg_idx; -} - /* ==================================================================== * RESULT PROCESSING FUNCTIONS * ==================================================================== */ @@ -458,9 +185,6 @@ int process_geo_int_result_async(CommandResponse* response, void* output, zval* if (response->response_type == Int) { ZVAL_LONG(return_value, response->int_value); return 1; - } else if (response->response_type == Null) { - ZVAL_NULL(return_value); - return 1; } ZVAL_LONG(return_value, 0); return 0; @@ -472,13 +196,7 @@ int process_geo_double_result_async(CommandResponse* response, void* output, zva return 0; } - if (response->response_type == Null) { - ZVAL_NULL(return_value); - return 1; - } else if (response->response_type == String) { - ZVAL_DOUBLE(return_value, atof(response->string_value)); - return 1; - } else if (response->response_type == Float) { + if (response->response_type == Float) { ZVAL_DOUBLE(return_value, response->float_value); return 1; } @@ -501,8 +219,6 @@ int process_geo_hash_result_async(CommandResponse* response, void* output, zval* if (element->response_type == String) { add_next_index_stringl( return_value, element->string_value, element->string_value_len); - } else if (element->response_type == Null) { - add_next_index_null(return_value); } } return 1; @@ -741,40 +457,11 @@ int execute_geo_generic_command(valkey_glide_object* valkey_glide, args, &arg_values, &arg_lens, &allocated_strings, &allocated_count); break; - - case GeoSearch: - allocated_strings = (char**) emalloc(10 * sizeof(char*)); - if (!allocated_strings) { - return 0; - } - arg_count = prepare_geo_search_args( - args, &arg_values, &arg_lens, &allocated_strings, &allocated_count); - break; - - case GeoSearchStore: - allocated_strings = (char**) emalloc(10 * sizeof(char*)); - if (!allocated_strings) { - return 0; - } - arg_count = prepare_geo_search_store_args( - args, &arg_values, &arg_lens, &allocated_strings, &allocated_count); - break; - default: /* Unsupported command type */ return 0; } - /* Check if argument preparation was successful */ - if (arg_count <= 0) { - if (allocated_strings) - efree(allocated_strings); - if (arg_values) - efree(arg_values); - if (arg_lens) - efree(arg_lens); - return 0; - } /* Check if we're in batch mode */ if (valkey_glide->is_in_batch_mode) { @@ -1109,16 +796,6 @@ int prepare_geo_search_unified_args(geo_search_params_t* params, char* lon_str = double_to_string(params->longitude, &lon_str_len); char* lat_str = double_to_string(params->latitude, &lat_str_len); - if (!lon_str || !lat_str) { - if (lon_str) - efree(lon_str); - if (lat_str) - efree(lat_str); - efree(*args_out); - efree(*args_len_out); - return 0; - } - (*args_out)[arg_idx] = (uintptr_t) lon_str; (*args_len_out)[arg_idx++] = lon_str_len; (*allocated_strings)[(*allocated_count)++] = lon_str; @@ -1136,12 +813,6 @@ int prepare_geo_search_unified_args(geo_search_params_t* params, size_t radius_str_len; char* radius_str = double_to_string(params->radius, &radius_str_len); - if (!radius_str) { - free_allocated_strings(*allocated_strings, *allocated_count); - efree(*args_out); - efree(*args_len_out); - return 0; - } (*args_out)[arg_idx] = (uintptr_t) radius_str; (*args_len_out)[arg_idx++] = radius_str_len; @@ -1155,17 +826,6 @@ int prepare_geo_search_unified_args(geo_search_params_t* params, char* width_str = double_to_string(params->width, &width_str_len); char* height_str = double_to_string(params->height, &height_str_len); - if (!width_str || !height_str) { - if (width_str) - efree(width_str); - if (height_str) - efree(height_str); - free_allocated_strings(*allocated_strings, *allocated_count); - efree(*args_out); - efree(*args_len_out); - return 0; - } - (*args_out)[arg_idx] = (uintptr_t) width_str; (*args_len_out)[arg_idx++] = width_str_len; (*allocated_strings)[(*allocated_count)++] = width_str; @@ -1192,12 +852,7 @@ int prepare_geo_search_unified_args(geo_search_params_t* params, size_t count_str_len; char* count_str = long_to_string(params->options.count, &count_str_len); - if (!count_str) { - free_allocated_strings(*allocated_strings, *allocated_count); - efree(*args_out); - efree(*args_len_out); - return 0; - } + (*args_out)[arg_idx] = (uintptr_t) count_str; (*args_len_out)[arg_idx++] = count_str_len; diff --git a/valkey_glide_str_commands.c b/valkey_glide_str_commands.c index a24e8c5a..2de5049e 100644 --- a/valkey_glide_str_commands.c +++ b/valkey_glide_str_commands.c @@ -237,17 +237,6 @@ static void build_sort_args(const char* key, uintptr_t* args = (uintptr_t*) emalloc(max_args * sizeof(uintptr_t)); unsigned long* args_len = (unsigned long*) emalloc(max_args * sizeof(unsigned long)); - if (!args || !args_len) { - if (args) - efree(args); - if (args_len) - efree(args_len); - *args_ptr = NULL; - *args_len_ptr = NULL; - *arg_count_ptr = 0; - return; - } - /* Current argument index */ unsigned long arg_idx = 0; From 631d85ddfc8e42fbe72df89ab0bf528ad0b90531 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 21 Sep 2025 17:40:10 +0300 Subject: [PATCH 07/46] lcs test update --- tests/ValkeyGlideTest.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 24680cc5..16b28859 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -275,18 +275,43 @@ public function testLcs() $this->assertTrue($this->valkey_glide->set($key1, '12244447777777')); $this->assertTrue($this->valkey_glide->set($key2, '6666662244441')); + // Test basic LCS $this->assertEquals('224444', $this->valkey_glide->lcs($key1, $key2)); + + // Test LCS with IDX $this->assertEquals( ['matches', [[[1, 6], [6, 11]]], 'len', 6], $this->valkey_glide->lcs($key1, $key2, ['idx']) ); + + // Test LCS with IDX and WITHMATCHLEN $this->assertEquals( ['matches', [[[1, 6], [6, 11], 6]], 'len', 6], $this->valkey_glide->lcs($key1, $key2, ['idx', 'withmatchlen']) ); + // Test LCS length only $this->assertEquals(6, $this->valkey_glide->lcs($key1, $key2, ['len'])); + // Test MINMATCHLEN with IDX (only matches of length >= 3) + $this->assertEquals( + ['matches', [[[1, 6], [6, 11]]], 'len', 6], + $this->valkey_glide->lcs($key1, $key2, ['idx', 'minmatchlen' => 3]) + ); + + // Test MINMATCHLEN with IDX and WITHMATCHLEN + + $this->assertEquals( + ['matches', [[[1, 6], [6, 11],6]], 'len', 6], + $this->valkey_glide->lcs($key1, $key2, ['idx', 'minmatchlen' => 3, 'withmatchlen']) + ); + + // Test MINMATCHLEN that filters all matches + $this->assertEquals( + ['matches', [], 'len', 6], + $this->valkey_glide->lcs($key1, $key2, ['idx', 'minmatchlen' => 10]) + ); + $this->valkey_glide->del([$key1, $key2]); } From 5ef53f8044798319a6672cc251333666294daec2 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 21 Sep 2025 18:13:15 +0300 Subject: [PATCH 08/46] tests are passing --- tests/ValkeyGlideTest.php | 14 ++- valkey_glide_x_commands.c | 226 +++++++++----------------------------- valkey_glide_x_common.c | 23 ---- valkey_glide_z.c | 57 ---------- valkey_glide_z_common.c | 61 +--------- 5 files changed, 67 insertions(+), 314 deletions(-) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 16b28859..89b878fc 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -3111,6 +3111,7 @@ public function testZX() $this->assertEquals(0, $this->valkey_glide->zRank('z', 'one')); $this->assertEquals(1, $this->valkey_glide->zRank('z', 'two')); $this->assertEquals(2, $this->valkey_glide->zRank('z', 'five')); + $this->assertEquals(2, $this->valkey_glide->zRank('z', 'five')); $this->assertEquals(2, $this->valkey_glide->zRevRank('z', 'one')); $this->assertEquals(1, $this->valkey_glide->zRevRank('z', 'two')); @@ -6281,7 +6282,8 @@ public function testXClaim() if (! $this->minVersionCheck('5.0')) { $this->markTestSkipped(); } - + foreach (['Pavlo', null] as $consumer) { + foreach ([0, 100] as $min_idle_time) { foreach ([false, true] as $justid) { foreach ([0, 10] as $retrycount) { @@ -6345,8 +6347,13 @@ public function testXClaim() $this->assertEquals($freturn, $fids); if ($retrycount || $tvalue !== null) { - $pending = $this->valkey_glide->xPending('s', 'group1', 0, '+', 1, 'Pavlo'); - + $pending = null; + if ($consumer != null) { + $pending = $this->valkey_glide->xPending('s', 'group1', 0, '+', 1, $consumer); + } else { + $pending = $this->valkey_glide->xPending('s', 'group1', 0, '+', 1); + } + if ($retrycount) { $this->assertEquals($pending[0][3], $retrycount); } @@ -6372,6 +6379,7 @@ public function testXClaim() } } } + } /* Make sure our XAUTOCLAIM handler works */ public function testXAutoClaim() diff --git a/valkey_glide_x_commands.c b/valkey_glide_x_commands.c index 1e626aed..c2064459 100644 --- a/valkey_glide_x_commands.c +++ b/valkey_glide_x_commands.c @@ -368,82 +368,38 @@ int execute_xrange_command(zval* object, int argc, zval* return_value, zend_clas char * key = NULL, *start = NULL, *end = NULL; size_t key_len = 0, start_len = 0, end_len = 0; zval* z_options = NULL; - long count = 0; + long count = -1; int options_created = 0; - /* Parse parameters - try different combinations based on argument count */ - if (argc == 4) { - /* xrange(key, start, end, count) */ - if (zend_parse_method_parameters(argc, - object, - "Osssl", - &object, - ce, - &key, - &key_len, - &start, - &start_len, - &end, - &end_len, - &count) == FAILURE) { - return 0; - } + /* Parse parameters: xrange(key, start, end [, count]) */ + if (zend_parse_method_parameters(argc, + object, + "Osss|l", + &object, + ce, + &key, + &key_len, + &start, + &start_len, + &end, + &end_len, + &count) == FAILURE) { + return 0; + } - /* Create options array with COUNT */ - z_options = emalloc(sizeof(zval)); - array_init(z_options); - add_assoc_long(z_options, "COUNT", count); - options_created = 1; - } else if (argc == 5) { - /* xrange(key, start, end, count, options) */ - if (zend_parse_method_parameters(argc, - object, - "Ossla", - &object, - ce, - &key, - &key_len, - &start, - &start_len, - &end, - &end_len, - &count, - &z_options) == FAILURE) { - return 0; - } + /* Get ValkeyGlide object */ + valkey_glide = VALKEY_GLIDE_PHP_ZVAL_GET_OBJECT(valkey_glide_object, object); - /* Add COUNT to existing options array or create new one */ - if (z_options && Z_TYPE_P(z_options) == IS_ARRAY) { - add_assoc_long(z_options, "COUNT", count); - } else { + /* If we have a Glide client, use it */ + if (valkey_glide->glide_client) { + /* Create options array if count is specified */ + if (count != -1) { z_options = emalloc(sizeof(zval)); array_init(z_options); add_assoc_long(z_options, "COUNT", count); options_created = 1; } - } else { - /* xrange(key, start, end [, options]) - original format for backward compatibility */ - if (zend_parse_method_parameters(argc, - object, - "Osss|a", - &object, - ce, - &key, - &key_len, - &start, - &start_len, - &end, - &end_len, - &z_options) == FAILURE) { - return 0; - } - } - - /* Get ValkeyGlide object */ - valkey_glide = VALKEY_GLIDE_PHP_ZVAL_GET_OBJECT(valkey_glide_object, object); - /* If we have a Glide client, use it */ - if (valkey_glide->glide_client) { /* Initialize the arguments structure */ x_command_args_t args = {0}; args.glide_client = valkey_glide->glide_client; @@ -485,82 +441,38 @@ int execute_xrevrange_command(zval* object, int argc, zval* return_value, zend_c char * key = NULL, *start = NULL, *end = NULL; size_t key_len = 0, start_len = 0, end_len = 0; zval* z_options = NULL; - long count = 0; + long count = -1; int options_created = 0; - /* Parse parameters - try different combinations based on argument count */ - if (argc == 4) { - /* xrevrange(key, end, start, count) */ - if (zend_parse_method_parameters(argc, - object, - "Osssl", - &object, - ce, - &key, - &key_len, - &end, - &end_len, - &start, - &start_len, - &count) == FAILURE) { - return 0; - } + /* Parse parameters: xrevrange(key, end, start [, count]) */ + if (zend_parse_method_parameters(argc, + object, + "Osss|l", + &object, + ce, + &key, + &key_len, + &end, + &end_len, + &start, + &start_len, + &count) == FAILURE) { + return 0; + } - /* Create options array with COUNT */ - z_options = emalloc(sizeof(zval)); - array_init(z_options); - add_assoc_long(z_options, "COUNT", count); - options_created = 1; - } else if (argc == 5) { - /* xrevrange(key, end, start, count, options) */ - if (zend_parse_method_parameters(argc, - object, - "Ossla", - &object, - ce, - &key, - &key_len, - &end, - &end_len, - &start, - &start_len, - &count, - &z_options) == FAILURE) { - return 0; - } + /* Get ValkeyGlide object */ + valkey_glide = VALKEY_GLIDE_PHP_ZVAL_GET_OBJECT(valkey_glide_object, object); - /* Add COUNT to existing options array or create new one */ - if (z_options && Z_TYPE_P(z_options) == IS_ARRAY) { - add_assoc_long(z_options, "COUNT", count); - } else { + /* If we have a Glide client, use it */ + if (valkey_glide->glide_client) { + /* Create options array if count is specified */ + if (count != -1) { z_options = emalloc(sizeof(zval)); array_init(z_options); add_assoc_long(z_options, "COUNT", count); options_created = 1; } - } else { - /* xrevrange(key, end, start [, options]) - original format for backward compatibility */ - if (zend_parse_method_parameters(argc, - object, - "Osss|a", - &object, - ce, - &key, - &key_len, - &end, - &end_len, - &start, - &start_len, - &z_options) == FAILURE) { - return 0; - } - } - - /* Get ValkeyGlide object */ - valkey_glide = VALKEY_GLIDE_PHP_ZVAL_GET_OBJECT(valkey_glide_object, object); - /* If we have a Glide client, use it */ - if (valkey_glide->glide_client) { /* Initialize the arguments structure */ x_command_args_t args = {0}; args.glide_client = valkey_glide->glide_client; @@ -627,7 +539,7 @@ int execute_xpending_command(zval* object, int argc, zval* return_value, zend_cl /* Format: xpending(key, group, start, end, count) */ if (zend_parse_method_parameters(argc, object, - "Osssl", + "Ossssl", &object, ce, &key, @@ -829,20 +741,7 @@ int execute_xreadgroup_command(zval* object, int argc, zval* return_value, zend_ add_assoc_long(z_options, "COUNT", count); options_created = 1; } else { - /* Try parsing as (group, consumer, streams, options) */ - if (zend_parse_method_parameters(argc, - object, - "Ossa", - &object, - ce, - &group, - &group_len, - &consumer, - &consumer_len, - &z_streams_and_ids, - &z_options) == FAILURE) { - return 0; - } + return 0; } } else if (argc == 5) { long block = -1; @@ -867,46 +766,21 @@ int execute_xreadgroup_command(zval* object, int argc, zval* return_value, zend_ add_assoc_long(z_options, "BLOCK", block); options_created = 1; } else { - /* Fallback to parsing as (group, consumer, streams, count, options) */ - if (zend_parse_method_parameters(argc, - object, - "Ossala", - &object, - ce, - &group, - &group_len, - &consumer, - &consumer_len, - &z_streams_and_ids, - &count, - &z_options) == FAILURE) { - return 0; - } - - /* Add COUNT to existing options array or create new one */ - if (z_options && Z_TYPE_P(z_options) == IS_ARRAY) { - add_assoc_long(z_options, "COUNT", count); - } else { - z_options = emalloc(sizeof(zval)); - array_init(z_options); - add_assoc_long(z_options, "COUNT", count); - options_created = 1; - } + return 0; } } else { - /* Parse as (group, consumer, streams [, options]) - original format for backward + /* Parse as (group, consumer, streams) - original format for backward * compatibility */ if (zend_parse_method_parameters(argc, object, - "Ossa|a", + "Ossa", &object, ce, &group, &group_len, &consumer, &consumer_len, - &z_streams_and_ids, - &z_options) == FAILURE) { + &z_streams_and_ids) == FAILURE) { return 0; } } diff --git a/valkey_glide_x_common.c b/valkey_glide_x_common.c index 7ec56192..17e3303e 100644 --- a/valkey_glide_x_common.c +++ b/valkey_glide_x_common.c @@ -340,16 +340,6 @@ int allocate_command_args(int count, uintptr_t** args_out, unsigned long** args_ return 1; } -/** - * Free command arguments arrays - */ -void free_command_args(uintptr_t* args, unsigned long* args_len) { - if (args) - efree(args); - if (args_len) - efree(args_len); -} - /** * Generic command execution framework with integrated batch support @@ -360,11 +350,6 @@ int execute_x_generic_command(valkey_glide_object* valkey_glide, void* result_ptr, x_result_processor_t process_result, zval* return_value) { - /* Check if valkey_glide object is valid */ - if (!valkey_glide) { - return 0; - } - /* Prepare arguments ONCE - single switch statement eliminates duplication */ uintptr_t* cmd_args = NULL; unsigned long* args_len = NULL; @@ -1648,10 +1633,6 @@ int prepare_x_claim_args(x_command_args_t* args, /* This string needs to be freed later */ // TODO: Track this string for cleanup - } else { - /* Failed to allocate memory for min_idle_time */ - free_command_args(*args_out, *args_len_out); - return 0; } /* Add all message IDs */ @@ -1816,10 +1797,6 @@ int prepare_x_autoclaim_args(x_command_args_t* args, /* This string needs to be freed later */ // TODO: Track this string for cleanup - } else { - /* Failed to allocate memory for min_idle_time */ - free_command_args(*args_out, *args_len_out); - return 0; } /* Add start ID */ diff --git a/valkey_glide_z.c b/valkey_glide_z.c index 2e1f0d81..5b538fa9 100644 --- a/valkey_glide_z.c +++ b/valkey_glide_z.c @@ -193,63 +193,6 @@ int execute_zmscore_command(zval* object, int argc, zval* return_value, zend_cla int member_count = 0; zval* z_args = NULL; - /* Method signature can be either of the following: - * - zMscore(string key, string member [, string ...]) - * - zMscore(string key, array members) - */ - - /* First, check if we have the second signature with an array */ - if (argc == 2) { - zval* z_members; - - /* Try to parse as (key, array) */ - if (zend_parse_method_parameters( - argc, object, "Osa", &object, ce, &key, &key_len, &z_members) == SUCCESS) { - /* Get ValkeyGlide object */ - valkey_glide_object* valkey_glide = - VALKEY_GLIDE_PHP_ZVAL_GET_OBJECT(valkey_glide_object, object); - - HashTable* ht_members = Z_ARRVAL_P(z_members); - member_count = zend_hash_num_elements(ht_members); - - if (member_count == 0) { - return 0; - } - - /* Create an array of members from the associative array */ - zval* members = emalloc(sizeof(zval) * member_count); - zval* data; - int idx = 0; - - ZEND_HASH_FOREACH_VAL(ht_members, data) { - ZVAL_COPY_VALUE(&members[idx++], data); - } - ZEND_HASH_FOREACH_END(); - - - /* Use framework for command execution */ - z_command_args_t args = {0}; - args.key = key; - args.key_len = key_len; - args.members = members; - args.member_count = member_count; - - - int result = execute_z_generic_command( - valkey_glide, ZMScore, &args, NULL, process_z_array_result, return_value); - - /* Clean up */ - efree(members); - if (valkey_glide->is_in_batch_mode) { - /* In batch mode, return $this for method chaining */ - ZVAL_COPY(return_value, object); - return 1; - } - - return result; - } - } - /* If we got here, either the array format failed or we have variadic args */ /* Parse as (key, member, member, ...) format */ if (zend_parse_method_parameters( diff --git a/valkey_glide_z_common.c b/valkey_glide_z_common.c index d802f21c..7f0fa03f 100644 --- a/valkey_glide_z_common.c +++ b/valkey_glide_z_common.c @@ -993,11 +993,7 @@ int prepare_z_store_args(z_command_args_t* args, char numkeys_str[32]; snprintf(numkeys_str, sizeof(numkeys_str), "%d", args->member_count); char* numkeys_str_copy = estrdup(numkeys_str); - if (!numkeys_str_copy) { - efree(*args_out); - efree(*args_len_out); - return 0; - } + (*args_out)[1] = (uintptr_t) numkeys_str_copy; (*args_len_out)[1] = strlen(numkeys_str); (*allocated_strings)[(*allocated_count)++] = numkeys_str_copy; @@ -1065,16 +1061,6 @@ int prepare_z_store_args(z_command_args_t* args, const char* agg_str = Z_STRVAL_P(store_opts.aggregate); char* agg_str_copy = estrdup(agg_str); - if (!agg_str_copy) { - /* Cleanup on error */ - int j; - for (j = 0; j < *allocated_count; j++) { - efree((*allocated_strings)[j]); - } - efree(*args_out); - efree(*args_len_out); - return 0; - } (*args_out)[offset] = (uintptr_t) agg_str_copy; (*args_len_out)[offset] = Z_STRLEN_P(store_opts.aggregate); @@ -1122,11 +1108,7 @@ int prepare_z_intercard_args(z_command_args_t* args, char numkeys_str[32]; snprintf(numkeys_str, sizeof(numkeys_str), "%d", args->member_count); char* numkeys_str_copy = estrdup(numkeys_str); - if (!numkeys_str_copy) { - efree(*args_out); - efree(*args_len_out); - return 0; - } + (*args_out)[0] = (uintptr_t) numkeys_str_copy; (*args_len_out)[0] = strlen(numkeys_str); (*allocated_strings)[(*allocated_count)++] = numkeys_str_copy; @@ -1158,16 +1140,7 @@ int prepare_z_intercard_args(z_command_args_t* args, char limit_str[32]; snprintf(limit_str, sizeof(limit_str), "%ld", limit); char* limit_str_copy = estrdup(limit_str); - if (!limit_str_copy) { - /* Cleanup on error */ - int j; - for (j = 0; j < *allocated_count; j++) { - efree((*allocated_strings)[j]); - } - efree(*args_out); - efree(*args_len_out); - return 0; - } + (*args_out)[offset] = (uintptr_t) limit_str_copy; (*args_len_out)[offset] = strlen(limit_str); (*allocated_strings)[(*allocated_count)++] = limit_str_copy; @@ -1221,11 +1194,7 @@ int prepare_z_union_args(z_command_args_t* args, char numkeys_str[32]; snprintf(numkeys_str, sizeof(numkeys_str), "%d", args->member_count); char* numkeys_str_copy = estrdup(numkeys_str); - if (!numkeys_str_copy) { - efree(*args_out); - efree(*args_len_out); - return 0; - } + (*args_out)[0] = (uintptr_t) numkeys_str_copy; (*args_len_out)[0] = strlen(numkeys_str); (*allocated_strings)[(*allocated_count)++] = numkeys_str_copy; @@ -1294,16 +1263,6 @@ int prepare_z_union_args(z_command_args_t* args, /* Add aggregate value */ const char* agg_str = Z_STRVAL_P(union_opts.aggregate); char* agg_str_copy = estrdup(agg_str); - if (!agg_str_copy) { - /* Cleanup on error */ - int j; - for (j = 0; j < *allocated_count; j++) { - efree((*allocated_strings)[j]); - } - efree(*args_out); - efree(*args_len_out); - return 0; - } (*args_out)[offset] = (uintptr_t) agg_str_copy; (*args_len_out)[offset] = Z_STRLEN_P(union_opts.aggregate); @@ -1571,11 +1530,7 @@ int prepare_z_zdiff_args(z_command_args_t* args, char numkeys_str[32]; snprintf(numkeys_str, sizeof(numkeys_str), "%d", args->member_count); char* numkeys_str_copy = estrdup(numkeys_str); - if (!numkeys_str_copy) { - efree(*args_out); - efree(*args_len_out); - return 0; - } + (*args_out)[0] = (uintptr_t) numkeys_str_copy; (*args_len_out)[0] = strlen(numkeys_str); (*allocated_strings)[(*allocated_count)++] = numkeys_str_copy; @@ -1644,11 +1599,7 @@ int prepare_z_randmember_args(z_command_args_t* args, char count_str[32]; snprintf(count_str, sizeof(count_str), "%ld", args->start); char* count_str_copy = estrdup(count_str); - if (!count_str_copy) { - efree(*args_out); - efree(*args_len_out); - return 0; - } + (*args_out)[arg_idx] = (uintptr_t) count_str_copy; (*args_len_out)[arg_idx] = strlen(count_str); (*allocated_strings)[(*allocated_count)++] = count_str_copy; From 94aad4ab7edeac5dd59942dac32faf1562864eb6 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 21 Sep 2025 18:23:13 +0300 Subject: [PATCH 09/46] dead code removal --- valkey_glide_list_common.c | 35 ----------------------------------- valkey_glide_s_common.c | 17 +++-------------- valkey_glide_z.c | 24 +----------------------- 3 files changed, 4 insertions(+), 72 deletions(-) diff --git a/valkey_glide_list_common.c b/valkey_glide_list_common.c index 4d8358ec..1655e582 100644 --- a/valkey_glide_list_common.c +++ b/valkey_glide_list_common.c @@ -102,22 +102,6 @@ int process_list_array_result_async(CommandResponse* response, void* output, zva response, return_value, COMMAND_RESPONSE_NOT_ASSOSIATIVE, false); } -/** - * Batch-compatible wrapper for boolean responses - */ -int process_list_bool_result_async(CommandResponse* response, void* output, zval* return_value) { - if (!response) - return 0; - - if (response->response_type == Bool) { - ZVAL_BOOL(return_value, response->bool_value); - return 1; - } else if (response->response_type == Ok) { - ZVAL_TRUE(return_value); - return 1; - } - return 0; -} /** * Batch-compatible wrapper for pop result responses (handles both single values and arrays) @@ -577,10 +561,6 @@ int prepare_list_key_count_args(list_command_args_t* args, if (args->count > 0) { size_t count_len; char* count_str = alloc_list_number_string(args->count, &count_len); - if (!count_str) { - free_list_command_args(*args_out, *args_len_out); - return 0; - } /* Track allocated string */ *allocated_strings = (char**) emalloc(sizeof(char*)); @@ -966,11 +946,6 @@ int prepare_list_position_args(list_command_args_t* args, size_t count_len; char* count_str = alloc_list_number_string(args->position_opts.count, &count_len); - if (!count_str) { - FREE_LIST_ALLOCATED_STRINGS(*allocated_strings, *allocated_count); - free_list_command_args(*args_out, *args_len_out); - return 0; - } (*allocated_strings)[*allocated_count] = count_str; (*allocated_count)++; @@ -1123,11 +1098,6 @@ int prepare_list_rem_args(list_command_args_t* args, /* Second argument: count */ size_t count_len; char* count_str = alloc_list_number_string(args->count, &count_len); - if (!count_str) { - FREE_LIST_ALLOCATED_STRINGS(*allocated_strings, *allocated_count); - free_list_command_args(*args_out, *args_len_out); - return 0; - } (*allocated_strings)[*allocated_count] = count_str; (*allocated_count)++; @@ -1394,11 +1364,6 @@ int prepare_list_mpop_args(list_command_args_t* args, size_t count_len; char* count_str = alloc_list_number_string(args->mpop_opts.count, &count_len); - if (!count_str) { - FREE_LIST_ALLOCATED_STRINGS(*allocated_strings, *allocated_count); - free_list_command_args(*args_out, *args_len_out); - return 0; - } (*allocated_strings)[*allocated_count] = count_str; (*allocated_count)++; diff --git a/valkey_glide_s_common.c b/valkey_glide_s_common.c index 494d8396..2404d998 100644 --- a/valkey_glide_s_common.c +++ b/valkey_glide_s_common.c @@ -193,10 +193,7 @@ int prepare_s_key_count_args(s_command_args_t* args, if (args->has_count) { char* count_str = alloc_long_string(args->count, NULL); - if (!count_str) { - cleanup_s_command_args(*args_out, *args_len_out); - return 0; - } + (*args_out)[1] = (uintptr_t) count_str; (*args_len_out)[1] = strlen(count_str); } @@ -378,11 +375,7 @@ int prepare_s_scan_args(s_command_args_t* args, arg_idx++; char* count_str = alloc_long_string(args->count, NULL); - if (!count_str) { - efree((void*) (*args_out)[has_key ? 1 : 0]); /* Free cursor_str */ - cleanup_s_command_args(*args_out, *args_len_out); - return 0; - } + (*args_out)[arg_idx] = (uintptr_t) count_str; (*args_len_out)[arg_idx] = strlen(count_str); arg_idx++; @@ -1763,11 +1756,7 @@ int execute_cluster_scan_command(const void* glide_client, /* Add COUNT */ if (has_count) { count_str = alloc_long_string(count, NULL); - if (!count_str) { - efree(args); - efree(args_len); - return 0; - } + args[idx] = (uintptr_t) "COUNT"; args_len[idx] = 5; idx++; diff --git a/valkey_glide_z.c b/valkey_glide_z.c index 5b538fa9..a206334f 100644 --- a/valkey_glide_z.c +++ b/valkey_glide_z.c @@ -1599,18 +1599,6 @@ int prepare_mpop_arguments(const void* glide_client, unsigned long* new_args_len = (unsigned long*) erealloc(args_len, arg_count * sizeof(unsigned long)); - if (!new_args || !new_args_len) { - efree(args); - efree(args_len); - efree(numkeys_str); - *numkeys_str_ptr = NULL; - if (is_blocking) { - efree(*timeout_str_ptr); - *timeout_str_ptr = NULL; - } - return 0; - } - args = new_args; args_len = new_args_len; @@ -1622,17 +1610,7 @@ int prepare_mpop_arguments(const void* glide_client, /* Add count value */ size_t count_len; char* count_str = alloc_list_number_string(count, &count_len); - if (!count_str) { - efree(args); - efree(args_len); - efree(numkeys_str); - *numkeys_str_ptr = NULL; - if (is_blocking) { - efree(*timeout_str_ptr); - *timeout_str_ptr = NULL; - } - return 0; - } + args[arg_idx] = (uintptr_t) count_str; args_len[arg_idx] = count_len; *count_str_ptr = count_str; From 391149085007e48f8f9305bdd6a0d8075ec46fc0 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Mon, 22 Sep 2025 10:48:15 +0300 Subject: [PATCH 10/46] updates --- tests/ValkeyGlideClusterTest.php | 21 +------------- tests/ValkeyGlideTest.php | 47 ++++++-------------------------- valkey_glide_cluster.stub.php | 11 ++++++-- valkey_glide_commands_3.c | 5 +++- valkey_glide_core_commands.c | 1 + valkey_glide_core_common.c | 31 ++++----------------- valkey_z_php_methods.c | 2 +- 7 files changed, 28 insertions(+), 90 deletions(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index e12fc7f2..e6277d56 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -122,26 +122,7 @@ public function testReconnectSelect() { $this->markTestSkipped(); } - public function testDoublePipeNoOp() - { - $this->markTestSkipped(); - } - public function testSwapDB() - { - $this->markTestSkipped(); - } - public function testConnectException() - { - $this->markTestSkipped(); - } - public function testTlsConnect() - { - $this->markTestSkipped(); - } - public function testConnectDatabaseSelect() - { - $this->markTestSkipped(); - } + public function testMove() { $this->markTestSkipped(); // Move is not supported in ValkeyGlideCluster diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 89b878fc..aaf2e269 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -3863,8 +3863,7 @@ public function testObject() /* GitHub issue #1211 (ignore redundant calls to pipeline or multi) */ public function testDoublePipeNoOp() - { - $this->markTestSkipped();//TODO + { /* Only the first pipeline should be honored */ for ($i = 0; $i < 6; $i++) { $this->valkey_glide->pipeline(); @@ -3906,8 +3905,7 @@ public function testDiscard() } public function testDifferentTypeString() - { - $this->markTestSkipped(); + { $key = '{hash}string'; $dkey = '{hash}' . __FUNCTION__; @@ -3970,8 +3968,7 @@ public function testDifferentTypeString() } public function testDifferentTypeList() - { - $this->markTestSkipped(); + { $key = '{hash}list'; $dkey = '{hash}' . __FUNCTION__; @@ -4031,8 +4028,7 @@ public function testDifferentTypeList() } public function testDifferentTypeSet() - { - $this->markTestSkipped(); + { $key = '{hash}set'; $dkey = '{hash}' . __FUNCTION__; $this->valkey_glide->del($key); @@ -4060,8 +4056,7 @@ public function testDifferentTypeSet() $this->assertFalse($this->valkey_glide->lSet($key, 0, 'newValue')); $this->assertFalse($this->valkey_glide->lrem($key, 'lvalue', 1)); $this->assertFalse($this->valkey_glide->lPop($key)); - $this->assertFalse($this->valkey_glide->rPop($key)); - $this->assertFalse($this->valkey_glide->rPoplPush($key, $dkey . 'lkey1')); + $this->assertFalse($this->valkey_glide->rPop($key)); // sorted sets I/F $this->assertFalse($this->valkey_glide->zAdd($key, 1, 'zValue1')); @@ -4092,8 +4087,7 @@ public function testDifferentTypeSet() } public function testDifferentTypeSortedSet() - { - $this->markTestSkipped(); + { $key = '{hash}sortedset'; $dkey = '{hash}' . __FUNCTION__; @@ -4153,8 +4147,7 @@ public function testDifferentTypeSortedSet() } public function testDifferentTypeHash() - { - $this->markTestSkipped(); + { $key = '{hash}hash'; $dkey = '{hash}hash'; @@ -4183,8 +4176,7 @@ public function testDifferentTypeHash() $this->assertFalse($this->valkey_glide->lSet($key, 0, 'newValue')); $this->assertFalse($this->valkey_glide->lrem($key, 'lvalue', 1)); $this->assertFalse($this->valkey_glide->lPop($key)); - $this->assertFalse($this->valkey_glide->rPop($key)); - $this->assertFalse($this->valkey_glide->rPoplPush($key, $dkey . 'lkey1')); + $this->assertFalse($this->valkey_glide->rPop($key)); // sets I/F $this->assertFalse($this->valkey_glide->sAdd($key, 'sValue1')); @@ -4214,29 +4206,6 @@ public function testDifferentTypeHash() $this->assertFalse($this->valkey_glide->zRemRangeByScore($key, 1, 2)); } - - - private function cartesianProduct(array $arrays) - { - $result = [[]]; - - foreach ($arrays as $array) { - $append = []; - foreach ($result as $product) { - foreach ($array as $item) { - $newProduct = $product; - $newProduct[] = $item; - $append[] = $newProduct; - } - } - - $result = $append; - } - - return $result; - } - - public function testDumpRestore() { diff --git a/valkey_glide_cluster.stub.php b/valkey_glide_cluster.stub.php index 7e528346..ddba4b0e 100644 --- a/valkey_glide_cluster.stub.php +++ b/valkey_glide_cluster.stub.php @@ -602,11 +602,16 @@ public function mset(array $key_values): ValkeyGlideCluster|bool; */ public function msetnx(array $key_values): ValkeyGlideCluster|array|false; - /* We only support ValkeyGlide::MULTI in ValkeyGlideCluster but take the argument - so we can test MULTI..EXEC with ValkeyGlideTest.php and in the event - we add pipeline support in the future. */ + /** + * @see ValkeyGlide::multi + */ public function multi(int $value = ValkeyGlide::MULTI): ValkeyGlideCluster|bool; + /** + * @see ValkeyGlide::pipeline + */ + public function pipeline(): bool|ValkeyGlideCluster; + /** * @see ValkeyGlide::object */ diff --git a/valkey_glide_commands_3.c b/valkey_glide_commands_3.c index bcd70f47..9bde1d12 100644 --- a/valkey_glide_commands_3.c +++ b/valkey_glide_commands_3.c @@ -637,6 +637,10 @@ static int initialize_batch_mode(valkey_glide_object* valkey_glide, return 0; } + if (valkey_glide->is_in_batch_mode) { + return 1; + } + /* Initialize batch mode */ valkey_glide->is_in_batch_mode = true; valkey_glide->batch_type = batch_type; @@ -679,7 +683,6 @@ int execute_multi_command(zval* object, int argc, zval* return_value, zend_class /* Execute a PIPELINE command using the Valkey Glide client - wrapper using common function */ int execute_pipeline_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) { valkey_glide_object* valkey_glide; - /* Parse parameters - pipeline takes no additional parameters */ if (zend_parse_method_parameters(argc, object, "O", &object, ce) == FAILURE) { return 0; diff --git a/valkey_glide_core_commands.c b/valkey_glide_core_commands.c index 092bf8a1..d120f734 100644 --- a/valkey_glide_core_commands.c +++ b/valkey_glide_core_commands.c @@ -243,6 +243,7 @@ static int process_set_result(CommandResponse* response, void* output, zval* ret struct set_result_data* data = (struct set_result_data*) output; if (!response) { + efree(output); return 0; } diff --git a/valkey_glide_core_common.c b/valkey_glide_core_common.c index c79e54ef..67cb77c6 100644 --- a/valkey_glide_core_common.c +++ b/valkey_glide_core_common.c @@ -98,14 +98,17 @@ int execute_core_command(valkey_glide_object* valkey_glide, /* Process result using appropriate handler */ if (result) { - if (!result->command_error && result->response) { + if (result->response) { /* Non-routed commands use standard processor */ res = processor(result->response, result_ptr, return_value); + } else { + ZVAL_FALSE(return_value); } /* Free the result - handle_string_response doesn't free it */ - free_command_result(result); + } else { + ZVAL_FALSE(return_value); } /* Cleanup */ @@ -340,9 +343,6 @@ int prepare_key_value_args(core_command_args_t* args, case CORE_ARG_TYPE_DOUBLE: total_args++; break; - case CORE_ARG_TYPE_MULTI_STRING: - total_args += args->args[i].data.multi_string_arg.count; - break; case CORE_ARG_TYPE_ARRAY: /* Count elements in the array */ total_args += args->args[i].data.array_arg.count; @@ -423,15 +423,6 @@ int prepare_key_value_args(core_command_args_t* args, break; } - case CORE_ARG_TYPE_MULTI_STRING: - for (int j = 0; j < args->args[i].data.multi_string_arg.count; j++) { - (*cmd_args)[arg_idx] = - (uintptr_t) args->args[i].data.multi_string_arg.values[j]; - (*cmd_args_len)[arg_idx] = args->args[i].data.multi_string_arg.lengths[j]; - arg_idx++; - } - break; - case CORE_ARG_TYPE_ARRAY: { /* Expand array elements into individual arguments */ zval* array = args->args[i].data.array_arg.array; @@ -595,9 +586,6 @@ int prepare_message_args(core_command_args_t* args, case CORE_ARG_TYPE_DOUBLE: total_args++; break; - case CORE_ARG_TYPE_MULTI_STRING: - total_args += args->args[i].data.multi_string_arg.count; - break; default: break; } @@ -646,15 +634,6 @@ int prepare_message_args(core_command_args_t* args, break; } - case CORE_ARG_TYPE_MULTI_STRING: - for (int j = 0; j < args->args[i].data.multi_string_arg.count; j++) { - (*cmd_args)[arg_idx] = - (uintptr_t) args->args[i].data.multi_string_arg.values[j]; - (*cmd_args_len)[arg_idx] = args->args[i].data.multi_string_arg.lengths[j]; - arg_idx++; - } - break; - default: break; } diff --git a/valkey_z_php_methods.c b/valkey_z_php_methods.c index a180bc37..c59ba151 100644 --- a/valkey_z_php_methods.c +++ b/valkey_z_php_methods.c @@ -615,7 +615,7 @@ FUNCTION_METHOD_IMPL(ValkeyGlide) MULTI_METHOD_IMPL(ValkeyGlide) /* }}} */ -/* {{{ proto bool ValkeyGlideCluster::pipeline() */ +/* {{{ proto bool ValkeyGlide::pipeline() */ PIPELINE_METHOD_IMPL(ValkeyGlide) /* }}} */ From 5355405ef7613779db968d150fa9782bb2fa4a9d Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 25 Sep 2025 16:57:44 +0300 Subject: [PATCH 11/46] fix memory leak --- valkey-glide | 2 +- valkey_glide_core_common.h | 1 - valkey_glide_x_common.c | 1 + valkey_glide_z_common.c | 3 +-- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/valkey-glide b/valkey-glide index 53b9fe2a..b549eff0 160000 --- a/valkey-glide +++ b/valkey-glide @@ -1 +1 @@ -Subproject commit 53b9fe2a939a80fbdad53e3c9e19c881d43a9efb +Subproject commit b549eff0340f693ecfe86f01a8a1a112098c8a98 diff --git a/valkey_glide_core_common.h b/valkey_glide_core_common.h index 2813ecf7..2f53ff1b 100644 --- a/valkey_glide_core_common.h +++ b/valkey_glide_core_common.h @@ -31,7 +31,6 @@ typedef enum { CORE_ARG_TYPE_LONG, CORE_ARG_TYPE_DOUBLE, CORE_ARG_TYPE_ARRAY, - CORE_ARG_TYPE_MULTI_STRING, CORE_ARG_TYPE_KEY_VALUE_PAIRS } core_arg_type_t; diff --git a/valkey_glide_x_common.c b/valkey_glide_x_common.c index 17e3303e..cd7d9af4 100644 --- a/valkey_glide_x_common.c +++ b/valkey_glide_x_common.c @@ -1901,6 +1901,7 @@ int prepare_x_trim_args(x_command_args_t* args, *allocated_strings = (char**) ecalloc(1, sizeof(char*)); *allocated_count = 0; *allocated_strings[*allocated_count] = limit_str_copy; + *allocated_count = 1; limit_str_copy[limit_str_len] = '\0'; (*args_out)[arg_idx] = (uintptr_t) limit_str_copy; diff --git a/valkey_glide_z_common.c b/valkey_glide_z_common.c index 7f0fa03f..cab09cb6 100644 --- a/valkey_glide_z_common.c +++ b/valkey_glide_z_common.c @@ -1817,8 +1817,7 @@ int process_z_array_result(CommandResponse* response, void* output, zval* return if (!response || !return_value) { return 0; } - /* Initialize return array */ - array_init(return_value); + /* Process the result */ int success = command_response_to_zval( response, return_value, COMMAND_RESPONSE_ASSOSIATIVE_ARRAY_MAP, true); From cf38092dba2c923d5fe874a0f5b20c26344de20b Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 25 Sep 2025 17:27:45 +0300 Subject: [PATCH 12/46] fix leaks in s commands --- valkey_glide_s_common.c | 207 +++++++++++++++++++++++++--------------- valkey_glide_s_common.h | 47 ++++++--- 2 files changed, 166 insertions(+), 88 deletions(-) diff --git a/valkey_glide_s_common.c b/valkey_glide_s_common.c index f840d828..0e55bdde 100644 --- a/valkey_glide_s_common.c +++ b/valkey_glide_s_common.c @@ -51,8 +51,13 @@ void cleanup_s_command_args(uintptr_t* args, unsigned long* args_len) { /** * Convert array of zvals to string arguments */ -int convert_zval_to_string_args( - zval* input, int count, uintptr_t** args_out, unsigned long** args_len_out, int offset) { +int convert_zval_to_string_args(zval* input, + int count, + uintptr_t** args_out, + unsigned long** args_len_out, + int offset, + char*** allocated_strings, + int* allocated_count) { int i; zval temp; @@ -76,6 +81,15 @@ int convert_zval_to_string_args( (*args_out)[offset + i] = (uintptr_t) str_copy; (*args_len_out)[offset + i] = str_len; zval_dtor(&temp); + + /* Track the allocated string */ + if (allocated_strings && allocated_count) { + /* Expand the allocated_strings array */ + *allocated_strings = + erealloc(*allocated_strings, (*allocated_count + 1) * sizeof(char*)); + (*allocated_strings)[*allocated_count] = str_copy; + (*allocated_count)++; + } } } @@ -107,7 +121,9 @@ char* alloc_long_string(long value, size_t* len_out) { */ int prepare_s_key_members_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { if (!args->glide_client || !args->key || args->key_len == 0 || !args->members || args->members_count <= 0) { return 0; @@ -124,7 +140,13 @@ int prepare_s_key_members_args(s_command_args_t* args, (*args_len_out)[0] = args->key_len; /* Convert and set member arguments */ - convert_zval_to_string_args(args->members, args->members_count, args_out, args_len_out, 1); + convert_zval_to_string_args(args->members, + args->members_count, + args_out, + args_len_out, + 1, + allocated_strings, + allocated_count); return arg_count; } @@ -134,7 +156,9 @@ int prepare_s_key_members_args(s_command_args_t* args, */ int prepare_s_key_only_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { if (!args->glide_client || !args->key || args->key_len == 0) { return 0; } @@ -154,7 +178,9 @@ int prepare_s_key_only_args(s_command_args_t* args, */ int prepare_s_key_member_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { if (!args->glide_client || !args->key || args->key_len == 0 || !args->member || args->member_len == 0) { return 0; @@ -177,7 +203,9 @@ int prepare_s_key_member_args(s_command_args_t* args, */ int prepare_s_key_count_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { if (!args->glide_client || !args->key || args->key_len == 0) { return 0; } @@ -196,6 +224,14 @@ int prepare_s_key_count_args(s_command_args_t* args, (*args_out)[1] = (uintptr_t) count_str; (*args_len_out)[1] = strlen(count_str); + + /* Track the allocated string */ + if (allocated_strings && allocated_count) { + *allocated_strings = + erealloc(*allocated_strings, (*allocated_count + 1) * sizeof(char*)); + (*allocated_strings)[*allocated_count] = count_str; + (*allocated_count)++; + } } return arg_count; @@ -206,7 +242,9 @@ int prepare_s_key_count_args(s_command_args_t* args, */ int prepare_s_multi_key_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { if (!args->glide_client || !args->keys || args->keys_count <= 0) { return 0; } @@ -215,7 +253,13 @@ int prepare_s_multi_key_args(s_command_args_t* args, return 0; } - convert_zval_to_string_args(args->keys, args->keys_count, args_out, args_len_out, 0); + convert_zval_to_string_args(args->keys, + args->keys_count, + args_out, + args_len_out, + 0, + allocated_strings, + allocated_count); return args->keys_count; } @@ -225,7 +269,9 @@ int prepare_s_multi_key_args(s_command_args_t* args, */ int prepare_s_multi_key_limit_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { if (!args->glide_client || !args->keys || args->keys_count <= 0) { return 0; } @@ -246,8 +292,21 @@ int prepare_s_multi_key_limit_args(s_command_args_t* args, (*args_out)[0] = (uintptr_t) numkeys_str; (*args_len_out)[0] = strlen(numkeys_str); + /* Track the allocated numkeys string */ + if (allocated_strings && allocated_count) { + *allocated_strings = erealloc(*allocated_strings, (*allocated_count + 1) * sizeof(char*)); + (*allocated_strings)[*allocated_count] = numkeys_str; + (*allocated_count)++; + } + /* Add keys */ - convert_zval_to_string_args(args->keys, args->keys_count, args_out, args_len_out, 1); + convert_zval_to_string_args(args->keys, + args->keys_count, + args_out, + args_len_out, + 1, + allocated_strings, + allocated_count); /* Add LIMIT if specified */ if (args->has_limit) { @@ -256,12 +315,19 @@ int prepare_s_multi_key_limit_args(s_command_args_t* args, char* limit_str = alloc_long_string(args->limit, NULL); if (!limit_str) { - efree((void*) (*args_out)[0]); /* Free numkeys_str */ cleanup_s_command_args(*args_out, *args_len_out); return 0; } (*args_out)[2 + args->keys_count] = (uintptr_t) limit_str; (*args_len_out)[2 + args->keys_count] = strlen(limit_str); + + /* Track the allocated limit string */ + if (allocated_strings && allocated_count) { + *allocated_strings = + erealloc(*allocated_strings, (*allocated_count + 1) * sizeof(char*)); + (*allocated_strings)[*allocated_count] = limit_str; + (*allocated_count)++; + } } return arg_count; @@ -272,7 +338,9 @@ int prepare_s_multi_key_limit_args(s_command_args_t* args, */ int prepare_s_dst_multi_key_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { if (!args->glide_client || !args->dst_key || args->dst_key_len == 0 || !args->keys || args->keys_count <= 0) { return 0; @@ -289,7 +357,13 @@ int prepare_s_dst_multi_key_args(s_command_args_t* args, (*args_len_out)[0] = args->dst_key_len; /* Add source keys */ - convert_zval_to_string_args(args->keys, args->keys_count, args_out, args_len_out, 1); + convert_zval_to_string_args(args->keys, + args->keys_count, + args_out, + args_len_out, + 1, + allocated_strings, + allocated_count); return arg_count; } @@ -299,7 +373,9 @@ int prepare_s_dst_multi_key_args(s_command_args_t* args, */ int prepare_s_two_key_member_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { if (!args->glide_client || !args->src_key || args->src_key_len == 0 || !args->dst_key || args->dst_key_len == 0 || !args->member || args->member_len == 0) { return 0; @@ -324,7 +400,9 @@ int prepare_s_two_key_member_args(s_command_args_t* args, */ int prepare_s_scan_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { if (!args->glide_client || !args->cursor) { return 0; } @@ -379,6 +457,14 @@ int prepare_s_scan_args(s_command_args_t* args, (*args_out)[arg_idx] = (uintptr_t) count_str; (*args_len_out)[arg_idx] = strlen(count_str); arg_idx++; + + /* Track the allocated count string */ + if (allocated_strings && allocated_count) { + *allocated_strings = + erealloc(*allocated_strings, (*allocated_count + 1) * sizeof(char*)); + (*allocated_strings)[*allocated_count] = count_str; + (*allocated_count)++; + } } /* Add TYPE if provided (SCAN only) */ @@ -641,34 +727,47 @@ int execute_s_generic_command(valkey_glide_object* valkey_glide, } + /* Initialize string tracking arrays */ + char** allocated_strings = NULL; + int allocated_count = 0; + /* Prepare arguments based on category */ switch (category) { case S_CMD_KEY_MEMBERS: - arg_count = prepare_s_key_members_args(args, &cmd_args, &args_len); + arg_count = prepare_s_key_members_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case S_CMD_KEY_ONLY: - arg_count = prepare_s_key_only_args(args, &cmd_args, &args_len); + arg_count = prepare_s_key_only_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case S_CMD_KEY_MEMBER: - arg_count = prepare_s_key_member_args(args, &cmd_args, &args_len); + arg_count = prepare_s_key_member_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case S_CMD_KEY_COUNT: - arg_count = prepare_s_key_count_args(args, &cmd_args, &args_len); + arg_count = prepare_s_key_count_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case S_CMD_MULTI_KEY: - arg_count = prepare_s_multi_key_args(args, &cmd_args, &args_len); + arg_count = prepare_s_multi_key_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case S_CMD_MULTI_KEY_LIMIT: - arg_count = prepare_s_multi_key_limit_args(args, &cmd_args, &args_len); + arg_count = prepare_s_multi_key_limit_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case S_CMD_DST_MULTI_KEY: - arg_count = prepare_s_dst_multi_key_args(args, &cmd_args, &args_len); + arg_count = prepare_s_dst_multi_key_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case S_CMD_TWO_KEY_MEMBER: - arg_count = prepare_s_two_key_member_args(args, &cmd_args, &args_len); + arg_count = prepare_s_two_key_member_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case S_CMD_SCAN: - arg_count = prepare_s_scan_args(args, &cmd_args, &args_len); + arg_count = prepare_s_scan_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; default: return 0; @@ -726,60 +825,14 @@ int execute_s_generic_command(valkey_glide_object* valkey_glide, cleanup: - /* Clean up allocated strings for specific categories */ - if (cmd_args && args_len) { - if (category == S_CMD_KEY_COUNT && args->has_count && arg_count > 1) { - efree((void*) cmd_args[1]); /* Free count string */ - } else if (category == S_CMD_MULTI_KEY_LIMIT) { - efree((void*) cmd_args[0]); /* Free numkeys string */ - if (args->has_limit && arg_count > 2 + args->keys_count) { - efree((void*) cmd_args[2 + args->keys_count]); /* Free limit string */ - } - } else if (category == S_CMD_SCAN) { - /* No need to free cursor string anymore - it's directly referenced */ - if (args->has_count) { - /* Find and free count string */ - int has_key = (args->key && args->key_len > 0); - int count_idx = (has_key ? 1 : 0) + 1 + (args->pattern ? 2 : 0) + 1; - if (count_idx < arg_count) { - efree((void*) cmd_args[count_idx]); - } - } - } - - /* Clean up allocated strings from convert_zval_to_string_args for member-based commands */ - if (category == S_CMD_KEY_MEMBERS || category == S_CMD_MULTI_KEY || - category == S_CMD_DST_MULTI_KEY) { - int start_idx = 0, count = 0; - - if (category == S_CMD_KEY_MEMBERS) { - start_idx = 1; /* Skip key, clean up member strings */ - count = args->members_count; - } else if (category == S_CMD_MULTI_KEY) { - start_idx = 0; /* Clean up all key strings */ - count = args->keys_count; - } else if (category == S_CMD_DST_MULTI_KEY) { - start_idx = 1; /* Skip destination key, clean up source key strings */ - count = args->keys_count; - } - - /* Free converted strings - check if they were allocated by our conversion function */ - for (int i = 0; i < count && (start_idx + i) < arg_count; i++) { - /* Only free if it's not pointing to original string data */ - if (args->members && category == S_CMD_KEY_MEMBERS) { - zval* element = &args->members[i]; - if (Z_TYPE_P(element) != IS_STRING && cmd_args[start_idx + i] != 0) { - efree((void*) cmd_args[start_idx + i]); - } - } else if (args->keys && - (category == S_CMD_MULTI_KEY || category == S_CMD_DST_MULTI_KEY)) { - zval* element = &args->keys[i]; - if (Z_TYPE_P(element) != IS_STRING && cmd_args[start_idx + i] != 0) { - efree((void*) cmd_args[start_idx + i]); - } - } + /* Free all allocated strings tracked by the prepare functions */ + if (allocated_strings && allocated_count > 0) { + for (int i = 0; i < allocated_count; i++) { + if (allocated_strings[i]) { + efree(allocated_strings[i]); } } + efree(allocated_strings); } cleanup_s_command_args(cmd_args, args_len); diff --git a/valkey_glide_s_common.h b/valkey_glide_s_common.h index 15cb58ed..bee7f7e2 100644 --- a/valkey_glide_s_common.h +++ b/valkey_glide_s_common.h @@ -134,36 +134,61 @@ int execute_s_generic_command(valkey_glide_object* valkey_glide, /* Argument preparation functions */ int prepare_s_key_members_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out); + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); int prepare_s_key_only_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out); + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); int prepare_s_key_member_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out); + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); int prepare_s_key_count_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out); + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); int prepare_s_multi_key_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out); + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); int prepare_s_multi_key_limit_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out); + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); int prepare_s_dst_multi_key_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out); + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); int prepare_s_two_key_member_args(s_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out); -int prepare_s_scan_args(s_command_args_t* args, uintptr_t** args_out, unsigned long** args_len_out); + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); +int prepare_s_scan_args(s_command_args_t* args, + uintptr_t** args_out, + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); /* Utility functions */ int allocate_s_command_args(int count, uintptr_t** args_out, unsigned long** args_len_out); void cleanup_s_command_args(uintptr_t* args, unsigned long* args_len); -int convert_zval_to_string_args( - zval* input, int count, uintptr_t** args_out, unsigned long** args_len_out, int offset); +int convert_zval_to_string_args(zval* input, + int count, + uintptr_t** args_out, + unsigned long** args_len_out, + int offset, + char*** allocated_strings, + int* allocated_count); char* alloc_long_string(long value, size_t* len_out); From b8c85f77bf7d0828d46b2a9f15ef0c26ac9a27c3 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 25 Sep 2025 18:35:38 +0300 Subject: [PATCH 13/46] x memory leaks fixes --- valkey_glide_x_common.c | 213 ++++++++++++++++++++++++++++++++-------- valkey_glide_x_common.h | 44 +++++++-- 2 files changed, 209 insertions(+), 48 deletions(-) diff --git a/valkey_glide_x_common.c b/valkey_glide_x_common.c index cd7d9af4..12449063 100644 --- a/valkey_glide_x_common.c +++ b/valkey_glide_x_common.c @@ -364,16 +364,20 @@ int execute_x_generic_command(valkey_glide_object* valkey_glide, case XGroupDelConsumer: case XGroupDestroy: case XGroupSetId: - arg_count = prepare_x_group_args(args, &cmd_args, &args_len); + arg_count = prepare_x_group_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case XLen: - arg_count = prepare_x_len_args(args, &cmd_args, &args_len); + arg_count = prepare_x_len_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case XDel: - arg_count = prepare_x_del_args(args, &cmd_args, &args_len); + arg_count = prepare_x_del_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case XAck: - arg_count = prepare_x_ack_args(args, &cmd_args, &args_len); + arg_count = prepare_x_ack_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case XAdd: arg_count = prepare_x_add_args( @@ -389,19 +393,24 @@ int execute_x_generic_command(valkey_glide_object* valkey_glide, args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case XPending: - arg_count = prepare_x_pending_args(args, &cmd_args, &args_len); + arg_count = prepare_x_pending_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case XRead: - arg_count = prepare_x_read_args(args, &cmd_args, &args_len); + arg_count = prepare_x_read_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case XReadGroup: - arg_count = prepare_x_readgroup_args(args, &cmd_args, &args_len); + arg_count = prepare_x_readgroup_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case XAutoClaim: - arg_count = prepare_x_autoclaim_args(args, &cmd_args, &args_len); + arg_count = prepare_x_autoclaim_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case XClaim: - arg_count = prepare_x_claim_args(args, &cmd_args, &args_len); + arg_count = prepare_x_claim_args( + args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; case XInfoGroups: case XInfoConsumers: @@ -888,12 +897,20 @@ int prepare_x_info_args(x_command_args_t* args, /** * Prepare arguments for XLEN command. */ -int prepare_x_len_args(x_command_args_t* args, uintptr_t** args_out, unsigned long** args_len_out) { +int prepare_x_len_args(x_command_args_t* args, + uintptr_t** args_out, + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { /* Check if client and key are valid */ if (!args->glide_client || !args->key || args->key_len <= 0) { return 0; } + /* Initialize allocated strings tracking */ + *allocated_strings = NULL; + *allocated_count = 0; + /* Allocate memory for arguments */ *args_out = (uintptr_t*) emalloc(sizeof(uintptr_t)); *args_len_out = (unsigned long*) emalloc(sizeof(unsigned long)); @@ -908,13 +925,21 @@ int prepare_x_len_args(x_command_args_t* args, uintptr_t** args_out, unsigned lo /** * Prepare arguments for XACK command. */ -int prepare_x_ack_args(x_command_args_t* args, uintptr_t** args_out, unsigned long** args_len_out) { +int prepare_x_ack_args(x_command_args_t* args, + uintptr_t** args_out, + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { /* Check if client and arguments are valid */ if (!args->glide_client || !args->key || args->key_len <= 0 || !args->group || args->group_len <= 0 || !args->ids || args->id_count <= 0) { return 0; } + /* Initialize allocated strings tracking */ + *allocated_strings = NULL; + *allocated_count = 0; + /* Prepare command arguments: key + group + IDs */ unsigned long arg_count = 2 + args->id_count; *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); @@ -947,13 +972,21 @@ int prepare_x_ack_args(x_command_args_t* args, uintptr_t** args_out, unsigned lo /** * Prepare arguments for XDEL command. */ -int prepare_x_del_args(x_command_args_t* args, uintptr_t** args_out, unsigned long** args_len_out) { +int prepare_x_del_args(x_command_args_t* args, + uintptr_t** args_out, + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { /* Check if client and arguments are valid */ if (!args->glide_client || !args->key || args->key_len <= 0 || !args->ids || args->id_count <= 0) { return 0; } + /* Initialize allocated strings tracking */ + *allocated_strings = NULL; + *allocated_count = 0; + /* Prepare command arguments: key + IDs */ unsigned long arg_count = 1 + args->id_count; *args_out = (uintptr_t*) emalloc(arg_count * sizeof(uintptr_t)); @@ -1182,12 +1215,18 @@ int prepare_x_add_args(x_command_args_t* args, */ int prepare_x_group_args(x_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { /* Check if client and arguments are valid */ if (!args->glide_client || !args->subcommand || args->subcommand_len <= 0 || !args->args) { return 0; } + /* Initialize allocated strings tracking */ + *allocated_strings = NULL; + *allocated_count = 0; + /* Allocate memory for arguments */ if (!allocate_command_args(args->args_count, args_out, args_len_out)) { return 0; @@ -1221,13 +1260,19 @@ int prepare_x_group_args(x_command_args_t* args, */ int prepare_x_pending_args(x_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { /* Check if client and arguments are valid */ if (!args->glide_client || !args->key || args->key_len <= 0 || !args->group || args->group_len <= 0) { return 0; } + /* Initialize allocated strings tracking */ + *allocated_strings = NULL; + *allocated_count = 0; + /* Count extra args based on options */ unsigned long extra_args = 0; if (args->pending_opts.start) @@ -1282,12 +1327,14 @@ int prepare_x_pending_args(x_command_args_t* args, memcpy(count_str_copy, count_str, count_str_len); count_str_copy[count_str_len] = '\0'; + /* Track this string for cleanup */ + *allocated_strings = (char**) ecalloc(1, sizeof(char*)); + (*allocated_strings)[0] = count_str_copy; + *allocated_count = 1; + (*args_out)[arg_idx] = (uintptr_t) count_str_copy; (*args_len_out)[arg_idx] = count_str_len; arg_idx++; - - /* This needs to be freed later */ - (*args_out)[arg_count - 1] = (uintptr_t) count_str_copy; } } @@ -1305,13 +1352,19 @@ int prepare_x_pending_args(x_command_args_t* args, */ int prepare_x_readgroup_args(x_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { /* Check if client and arguments are valid */ if (!args->glide_client || !args->group || args->group_len <= 0 || !args->consumer || args->consumer_len <= 0 || !args->streams || !args->ids) { return 0; } + /* Initialize allocated strings tracking */ + *allocated_strings = NULL; + *allocated_count = 0; + /* Get the number of streams and IDs */ HashTable* streams_ht = Z_ARRVAL_P(args->streams); HashTable* ids_ht = Z_ARRVAL_P(args->ids); @@ -1340,6 +1393,17 @@ int prepare_x_readgroup_args(x_command_args_t* args, return 0; } + /* Allocate memory to track dynamic strings if needed */ + int max_strings = 0; + if (args->read_opts.has_count) + max_strings++; + if (args->read_opts.has_block) + max_strings++; + + if (max_strings > 0) { + *allocated_strings = (char**) ecalloc(max_strings, sizeof(char*)); + } + /* Set arguments */ unsigned int arg_idx = 0; @@ -1373,6 +1437,10 @@ int prepare_x_readgroup_args(x_command_args_t* args, memcpy(count_str_copy, count_str, count_str_len); count_str_copy[count_str_len] = '\0'; + /* Track this string for cleanup */ + (*allocated_strings)[*allocated_count] = count_str_copy; + (*allocated_count)++; + (*args_out)[arg_idx] = (uintptr_t) count_str_copy; (*args_len_out)[arg_idx] = count_str_len; arg_idx++; @@ -1396,6 +1464,10 @@ int prepare_x_readgroup_args(x_command_args_t* args, memcpy(block_str_copy, block_str, block_str_len); block_str_copy[block_str_len] = '\0'; + /* Track this string for cleanup */ + (*allocated_strings)[*allocated_count] = block_str_copy; + (*allocated_count)++; + (*args_out)[arg_idx] = (uintptr_t) block_str_copy; (*args_len_out)[arg_idx] = block_str_len; arg_idx++; @@ -1446,12 +1518,18 @@ int prepare_x_readgroup_args(x_command_args_t* args, */ int prepare_x_read_args(x_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { /* Check if client and arguments are valid */ if (!args->glide_client || !args->streams || !args->ids) { return 0; } + /* Initialize allocated strings tracking */ + *allocated_strings = NULL; + *allocated_count = 0; + /* Get the number of streams and IDs */ HashTable* streams_ht = Z_ARRVAL_P(args->streams); HashTable* ids_ht = Z_ARRVAL_P(args->ids); @@ -1480,6 +1558,17 @@ int prepare_x_read_args(x_command_args_t* args, return 0; } + /* Allocate memory to track dynamic strings if needed */ + int max_strings = 0; + if (args->read_opts.has_count) + max_strings++; + if (args->read_opts.has_block) + max_strings++; + + if (max_strings > 0) { + *allocated_strings = (char**) ecalloc(max_strings, sizeof(char*)); + } + /* Set arguments */ unsigned int arg_idx = 0; @@ -1500,6 +1589,10 @@ int prepare_x_read_args(x_command_args_t* args, memcpy(count_str_copy, count_str, count_str_len); count_str_copy[count_str_len] = '\0'; + /* Track this string for cleanup */ + (*allocated_strings)[*allocated_count] = count_str_copy; + (*allocated_count)++; + (*args_out)[arg_idx] = (uintptr_t) count_str_copy; (*args_len_out)[arg_idx] = count_str_len; arg_idx++; @@ -1523,6 +1616,10 @@ int prepare_x_read_args(x_command_args_t* args, memcpy(block_str_copy, block_str, block_str_len); block_str_copy[block_str_len] = '\0'; + /* Track this string for cleanup */ + (*allocated_strings)[*allocated_count] = block_str_copy; + (*allocated_count)++; + (*args_out)[arg_idx] = (uintptr_t) block_str_copy; (*args_len_out)[arg_idx] = block_str_len; arg_idx++; @@ -1573,7 +1670,9 @@ int prepare_x_read_args(x_command_args_t* args, */ int prepare_x_claim_args(x_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { /* Check if client and arguments are valid */ if (!args->glide_client || !args->key || args->key_len <= 0 || !args->group || args->group_len <= 0 || !args->consumer || args->consumer_len <= 0 || !args->ids || @@ -1581,6 +1680,10 @@ int prepare_x_claim_args(x_command_args_t* args, return 0; } + /* Initialize allocated strings tracking */ + *allocated_strings = NULL; + *allocated_count = 0; + /* Count options */ unsigned long extra_args = 0; if (args->claim_opts.has_idle) @@ -1602,6 +1705,18 @@ int prepare_x_claim_args(x_command_args_t* args, return 0; } + /* Calculate maximum number of strings we might allocate */ + int max_strings = 1; /* min_idle_time */ + if (args->claim_opts.has_idle) + max_strings++; + if (args->claim_opts.has_time) + max_strings++; + if (args->claim_opts.has_retrycount) + max_strings++; + + /* Allocate memory to track dynamic strings */ + *allocated_strings = (char**) ecalloc(max_strings, sizeof(char*)); + /* Set key, group, consumer, min_idle_time */ unsigned int arg_idx = 0; (*args_out)[arg_idx] = (uintptr_t) args->key; @@ -1627,12 +1742,13 @@ int prepare_x_claim_args(x_command_args_t* args, memcpy(min_idle_str_copy, min_idle_str, min_idle_str_len); min_idle_str_copy[min_idle_str_len] = '\0'; + /* Track this string for cleanup */ + (*allocated_strings)[*allocated_count] = min_idle_str_copy; + (*allocated_count)++; + (*args_out)[arg_idx] = (uintptr_t) min_idle_str_copy; (*args_len_out)[arg_idx] = min_idle_str_len; arg_idx++; - - /* This string needs to be freed later */ - // TODO: Track this string for cleanup } /* Add all message IDs */ @@ -1664,12 +1780,13 @@ int prepare_x_claim_args(x_command_args_t* args, memcpy(idle_str_copy, idle_str, idle_str_len); idle_str_copy[idle_str_len] = '\0'; + /* Track this string for cleanup */ + (*allocated_strings)[*allocated_count] = idle_str_copy; + (*allocated_count)++; + (*args_out)[arg_idx] = (uintptr_t) idle_str_copy; (*args_len_out)[arg_idx] = idle_str_len; arg_idx++; - - /* This string needs to be freed later */ - // TODO: Track this string for cleanup } } @@ -1689,12 +1806,13 @@ int prepare_x_claim_args(x_command_args_t* args, memcpy(time_str_copy, time_str, time_str_len); time_str_copy[time_str_len] = '\0'; + /* Track this string for cleanup */ + (*allocated_strings)[*allocated_count] = time_str_copy; + (*allocated_count)++; + (*args_out)[arg_idx] = (uintptr_t) time_str_copy; (*args_len_out)[arg_idx] = time_str_len; arg_idx++; - - /* This string needs to be freed later */ - // TODO: Track this string for cleanup } } @@ -1714,12 +1832,13 @@ int prepare_x_claim_args(x_command_args_t* args, memcpy(retry_str_copy, retry_str, retry_str_len); retry_str_copy[retry_str_len] = '\0'; + /* Track this string for cleanup */ + (*allocated_strings)[*allocated_count] = retry_str_copy; + (*allocated_count)++; + (*args_out)[arg_idx] = (uintptr_t) retry_str_copy; (*args_len_out)[arg_idx] = retry_str_len; arg_idx++; - - /* This string needs to be freed later */ - // TODO: Track this string for cleanup } } @@ -1743,7 +1862,9 @@ int prepare_x_claim_args(x_command_args_t* args, */ int prepare_x_autoclaim_args(x_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out) { + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count) { /* Check if client and arguments are valid */ if (!args->glide_client || !args->key || args->key_len <= 0 || !args->group || args->group_len <= 0 || !args->consumer || args->consumer_len <= 0 || !args->start || @@ -1751,6 +1872,10 @@ int prepare_x_autoclaim_args(x_command_args_t* args, return 0; } + /* Initialize allocated strings tracking */ + *allocated_strings = NULL; + *allocated_count = 0; + /* Count options */ unsigned long extra_args = 0; if (args->claim_opts.has_count) @@ -1766,6 +1891,14 @@ int prepare_x_autoclaim_args(x_command_args_t* args, return 0; } + /* Calculate maximum number of strings we might allocate */ + int max_strings = 1; /* min_idle_time */ + if (args->claim_opts.has_count) + max_strings++; + + /* Allocate memory to track dynamic strings */ + *allocated_strings = (char**) ecalloc(max_strings, sizeof(char*)); + /* Set key, group, consumer */ unsigned int arg_idx = 0; (*args_out)[arg_idx] = (uintptr_t) args->key; @@ -1791,12 +1924,13 @@ int prepare_x_autoclaim_args(x_command_args_t* args, memcpy(min_idle_str_copy, min_idle_str, min_idle_str_len); min_idle_str_copy[min_idle_str_len] = '\0'; + /* Track this string for cleanup */ + (*allocated_strings)[*allocated_count] = min_idle_str_copy; + (*allocated_count)++; + (*args_out)[arg_idx] = (uintptr_t) min_idle_str_copy; (*args_len_out)[arg_idx] = min_idle_str_len; arg_idx++; - - /* This string needs to be freed later */ - // TODO: Track this string for cleanup } /* Add start ID */ @@ -1821,12 +1955,13 @@ int prepare_x_autoclaim_args(x_command_args_t* args, memcpy(count_str_copy, count_str, count_str_len); count_str_copy[count_str_len] = '\0'; + /* Track this string for cleanup */ + (*allocated_strings)[*allocated_count] = count_str_copy; + (*allocated_count)++; + (*args_out)[arg_idx] = (uintptr_t) count_str_copy; (*args_len_out)[arg_idx] = count_str_len; arg_idx++; - - /* This string needs to be freed later */ - // TODO: Track this string for cleanup } } diff --git a/valkey_glide_x_common.h b/valkey_glide_x_common.h index 2519bd5f..9ceb6e0e 100644 --- a/valkey_glide_x_common.h +++ b/valkey_glide_x_common.h @@ -209,11 +209,23 @@ int execute_x_generic_command(valkey_glide_object* valkey_glide, zval* return_value); /* Argument preparation */ -int prepare_x_len_args(x_command_args_t* args, uintptr_t** args_out, unsigned long** args_len_out); +int prepare_x_len_args(x_command_args_t* args, + uintptr_t** args_out, + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); -int prepare_x_del_args(x_command_args_t* args, uintptr_t** args_out, unsigned long** args_len_out); +int prepare_x_del_args(x_command_args_t* args, + uintptr_t** args_out, + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); -int prepare_x_ack_args(x_command_args_t* args, uintptr_t** args_out, unsigned long** args_len_out); +int prepare_x_ack_args(x_command_args_t* args, + uintptr_t** args_out, + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); int prepare_x_add_args(x_command_args_t* args, uintptr_t** args_out, @@ -235,25 +247,39 @@ int prepare_x_range_args(x_command_args_t* args, int prepare_x_claim_args(x_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out); + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); int prepare_x_autoclaim_args(x_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out); + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); int prepare_x_group_args(x_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out); + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); int prepare_x_pending_args(x_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out); + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); -int prepare_x_read_args(x_command_args_t* args, uintptr_t** args_out, unsigned long** args_len_out); +int prepare_x_read_args(x_command_args_t* args, + uintptr_t** args_out, + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); int prepare_x_readgroup_args(x_command_args_t* args, uintptr_t** args_out, - unsigned long** args_len_out); + unsigned long** args_len_out, + char*** allocated_strings, + int* allocated_count); int prepare_x_info_args(x_command_args_t* args, uintptr_t** args_out, From 5c7ea43b66bc5075b09139f9a57e601e73d70280 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 25 Sep 2025 19:13:07 +0300 Subject: [PATCH 14/46] x memory leaks fixes --- valkey_glide_x_common.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/valkey_glide_x_common.c b/valkey_glide_x_common.c index 12449063..f1031f09 100644 --- a/valkey_glide_x_common.c +++ b/valkey_glide_x_common.c @@ -430,26 +430,33 @@ int execute_x_generic_command(valkey_glide_object* valkey_glide, efree(cmd_args); if (args_len) efree(args_len); + for (int i = 0; i < allocated_count; i++) { + if (allocated_strings[i]) { + efree(allocated_strings[i]); + } + } if (allocated_strings) efree(allocated_strings); return 0; } if (valkey_glide->is_in_batch_mode) { - int result = buffer_command_for_batch(valkey_glide, - cmd_type, - cmd_args, - args_len, - arg_count, - - result_ptr, - process_result); + int result = buffer_command_for_batch( + valkey_glide, cmd_type, cmd_args, args_len, arg_count, result_ptr, process_result); if (cmd_args) efree(cmd_args); if (args_len) efree(args_len); + for (int i = 0; i < allocated_count; i++) { + if (allocated_strings[i]) { + efree(allocated_strings[i]); + } + } + if (allocated_strings) + efree(allocated_strings); + return result; } @@ -1068,6 +1075,7 @@ int prepare_x_range_args(x_command_args_t* args, *allocated_strings = (char**) ecalloc(1, sizeof(char*)); *allocated_count = 0; *allocated_strings[*allocated_count] = count_str_copy; + (*allocated_count)++; if (count_str_copy) { memcpy(count_str_copy, count_str, count_str_len); count_str_copy[count_str_len] = '\0'; From 5b4275e93b30eec027aedf7bba043faa7ad26f26 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 25 Sep 2025 19:17:34 +0300 Subject: [PATCH 15/46] updates --- valkey_glide_z_common.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/valkey_glide_z_common.c b/valkey_glide_z_common.c index cab09cb6..cb0a8577 100644 --- a/valkey_glide_z_common.c +++ b/valkey_glide_z_common.c @@ -546,6 +546,11 @@ int execute_z_generic_command(valkey_glide_object* valkey_glide, efree(arg_values); if (arg_lens) efree(arg_lens); + for (int i = 0; i < allocated_count; i++) { + if (allocated_strings[i]) { + efree(allocated_strings[i]); + } + } if (allocated_strings) efree(allocated_strings); return 0; @@ -566,6 +571,14 @@ int execute_z_generic_command(valkey_glide_object* valkey_glide, if (arg_lens) efree(arg_lens); + for (int i = 0; i < allocated_count; i++) { + if (allocated_strings[i]) { + efree(allocated_strings[i]); + } + } + if (allocated_strings) + efree(allocated_strings); + return result; } /* Execute the command */ From c44387f9f21192b173b82ed27fe25d7af946f135 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 25 Sep 2025 19:27:52 +0300 Subject: [PATCH 16/46] updates --- command_response.c | 5 ++++- valkey_glide_hash_common.c | 20 +++----------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/command_response.c b/command_response.c index 9aeb1093..c8ad0bbf 100644 --- a/command_response.c +++ b/command_response.c @@ -495,8 +495,9 @@ int command_response_to_zval(CommandResponse* response, response->array_value_len, use_associative_array); #endif - array_init(output); + if (use_associative_array == COMMAND_RESPONSE_SCAN_ASSOSIATIVE_ARRAY) { + array_init(output); for (int64_t i = 0; i + 1 < response->array_value_len; i += 2) { zval field, value; @@ -525,6 +526,7 @@ int command_response_to_zval(CommandResponse* response, __LINE__, response->array_value[0].response_type); #endif + array_init(output); for (int64_t i = 0; i < response->array_value_len; ++i) { command_response_to_zval(&response->array_value[i], &field, @@ -545,6 +547,7 @@ int command_response_to_zval(CommandResponse* response, } } } else { + array_init(output); for (int64_t i = 0; i < response->array_value_len; i++) { zval value; diff --git a/valkey_glide_hash_common.c b/valkey_glide_hash_common.c index 8e1d6697..9554ab48 100644 --- a/valkey_glide_hash_common.c +++ b/valkey_glide_hash_common.c @@ -577,19 +577,8 @@ int process_h_string_result_async(CommandResponse* response, void* output, zval* if (!response) return 0; - char* result_str = NULL; - size_t result_len = 0; - - if (response->response_type == String) { - result_str = estrndup(response->string_value, response->string_value_len); - result_len = response->string_value_len; - ZVAL_STRINGL(return_value, result_str, result_len); - return 1; - } else if (response->response_type == Null) { - ZVAL_FALSE(return_value); - return 1; - } - return 0; + return command_response_to_zval( + response, return_value, COMMAND_RESPONSE_NOT_ASSOSIATIVE, false); } /** @@ -597,17 +586,14 @@ int process_h_string_result_async(CommandResponse* response, void* output, zval* */ int process_h_array_result_async(CommandResponse* response, void* output, zval* return_value) { /* Initialize return array */ - array_init(return_value); return command_response_to_zval( response, (zval*) return_value, COMMAND_RESPONSE_NOT_ASSOSIATIVE, false); } - /** * Batch-compatible wrapper for map responses */ int process_h_map_result_async(CommandResponse* response, void* output, zval* return_value) { - array_init(return_value); - return command_response_to_zval( + return command_response_to_zval( response, (zval*) return_value, COMMAND_RESPONSE_ASSOSIATIVE_ARRAY_MAP, false); } From 1b995435f4b5b2290bff44993d4f49c1a78b13fb Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Fri, 26 Sep 2025 17:32:32 +0300 Subject: [PATCH 17/46] memory leak fixes --- command_response.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/command_response.c b/command_response.c index c8ad0bbf..42c0e0c8 100644 --- a/command_response.c +++ b/command_response.c @@ -570,7 +570,7 @@ int command_response_to_zval(CommandResponse* response, case Map: // printf("%s:%d - CommandResponse is Map with length: %ld\n", __FILE__, // __LINE__, response->array_value_len); - array_init(output); + // Special handling for FUNCTION command - skip server address wrapper if (use_associative_array == COMMAND_RESPONSE_ASSOSIATIVE_ARRAY_MAP_FUNCTION && @@ -596,6 +596,7 @@ int command_response_to_zval(CommandResponse* response, } // Normal Map processing + array_init(output); for (int i = 0; i < response->array_value_len; i++) { zval key, value; CommandResponse* element = &response->array_value[i]; @@ -784,7 +785,6 @@ int command_response_to_stream_zval(CommandResponse* response, zval* output) { case Null: /* If the response is Null, set output to NULL */ // printf("%s:%d - DEBUG: Response is Null\n", __FILE__, __LINE__); - array_init(output); break; default: From 8aee3694657d36c80422e441f7a51e802f98ad45 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 28 Sep 2025 14:36:02 +0300 Subject: [PATCH 18/46] fix lint --- valkey_glide_hash_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valkey_glide_hash_common.c b/valkey_glide_hash_common.c index 9554ab48..63602e4b 100644 --- a/valkey_glide_hash_common.c +++ b/valkey_glide_hash_common.c @@ -593,7 +593,7 @@ int process_h_array_result_async(CommandResponse* response, void* output, zval* * Batch-compatible wrapper for map responses */ int process_h_map_result_async(CommandResponse* response, void* output, zval* return_value) { - return command_response_to_zval( + return command_response_to_zval( response, (zval*) return_value, COMMAND_RESPONSE_ASSOSIATIVE_ARRAY_MAP, false); } From c1740c6ce8f04d55787d20ebafb79d964e479ff8 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 28 Sep 2025 14:37:18 +0300 Subject: [PATCH 19/46] fix PHP lint --- tests/ValkeyGlideClusterTest.php | 11 +- tests/ValkeyGlideTest.php | 502 +++++++++++++++++++------------ 2 files changed, 319 insertions(+), 194 deletions(-) diff --git a/tests/ValkeyGlideClusterTest.php b/tests/ValkeyGlideClusterTest.php index b89f0b6e..5753526c 100644 --- a/tests/ValkeyGlideClusterTest.php +++ b/tests/ValkeyGlideClusterTest.php @@ -141,7 +141,7 @@ public function testFlushDB() $this->assertTrue($this->valkey_glide->flushdb($key, false)); $this->assertTrue($this->valkey_glide->flushdb($key, true)); } - + public function testFunction() { $this->markTestSkipped(); @@ -196,7 +196,7 @@ public function testPing() for ($i = 0; $i < 20; $i++) { $this->assertTrue($this->valkey_glide->ping(['type' => 'primarySlotKey', 'key' => "key:$i"])); $this->assertEquals('BEEP', $this->valkey_glide->ping(['type' => 'primarySlotKey', 'key' => "key:$i"], 'BEEP')); - } + } } public function testRandomKey() @@ -222,7 +222,7 @@ public function testEcho() } public function testSortPrefix() - { + { $this->valkey_glide->del('some-prefix:some-item'); $this->valkey_glide->sadd('some-prefix:some-item', 1); $this->valkey_glide->sadd('some-prefix:some-item', 2); @@ -231,7 +231,6 @@ public function testSortPrefix() // Kill our set/prefix $this->valkey_glide->del('some-prefix:some-item'); - } public function testDBSize() @@ -885,6 +884,4 @@ public function testReplyLiteral() // Reset $this->valkey_glide->setOption(ValkeyGlide::OPT_REPLY_LITERAL, false); } - - -} +} diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index aaf2e269..8aba27c5 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -277,13 +277,13 @@ public function testLcs() // Test basic LCS $this->assertEquals('224444', $this->valkey_glide->lcs($key1, $key2)); - + // Test LCS with IDX $this->assertEquals( ['matches', [[[1, 6], [6, 11]]], 'len', 6], $this->valkey_glide->lcs($key1, $key2, ['idx']) ); - + // Test LCS with IDX and WITHMATCHLEN $this->assertEquals( ['matches', [[[1, 6], [6, 11], 6]], 'len', 6], @@ -300,7 +300,7 @@ public function testLcs() ); // Test MINMATCHLEN with IDX and WITHMATCHLEN - + $this->assertEquals( ['matches', [[[1, 6], [6, 11],6]], 'len', 6], $this->valkey_glide->lcs($key1, $key2, ['idx', 'minmatchlen' => 3, 'withmatchlen']) @@ -1121,7 +1121,7 @@ public function testTouch() $idle1 = $this->valkey_glide->object('idletime', '{idle}1'); $idle2 = $this->valkey_glide->object('idletime', '{idle}2'); - + /* We're not testing if idle is 0 because CPU scheduling on GitHub CI * potatoes can cause that to erroneously fail. */ @@ -1132,7 +1132,7 @@ public function testTouch() $idle1 = $this->valkey_glide->object('idletime', '{idle}1'); $idle2 = $this->valkey_glide->object('idletime', '{idle}2'); - + /* We're not testing if idle is 0 because CPU scheduling on GitHub CI * potatoes can cause that to erroneously fail. */ @@ -3863,7 +3863,7 @@ public function testObject() /* GitHub issue #1211 (ignore redundant calls to pipeline or multi) */ public function testDoublePipeNoOp() - { + { /* Only the first pipeline should be honored */ for ($i = 0; $i < 6; $i++) { $this->valkey_glide->pipeline(); @@ -3905,7 +3905,7 @@ public function testDiscard() } public function testDifferentTypeString() - { + { $key = '{hash}string'; $dkey = '{hash}' . __FUNCTION__; @@ -3968,7 +3968,7 @@ public function testDifferentTypeString() } public function testDifferentTypeList() - { + { $key = '{hash}list'; $dkey = '{hash}' . __FUNCTION__; @@ -4028,7 +4028,7 @@ public function testDifferentTypeList() } public function testDifferentTypeSet() - { + { $key = '{hash}set'; $dkey = '{hash}' . __FUNCTION__; $this->valkey_glide->del($key); @@ -4056,7 +4056,7 @@ public function testDifferentTypeSet() $this->assertFalse($this->valkey_glide->lSet($key, 0, 'newValue')); $this->assertFalse($this->valkey_glide->lrem($key, 'lvalue', 1)); $this->assertFalse($this->valkey_glide->lPop($key)); - $this->assertFalse($this->valkey_glide->rPop($key)); + $this->assertFalse($this->valkey_glide->rPop($key)); // sorted sets I/F $this->assertFalse($this->valkey_glide->zAdd($key, 1, 'zValue1')); @@ -4087,7 +4087,7 @@ public function testDifferentTypeSet() } public function testDifferentTypeSortedSet() - { + { $key = '{hash}sortedset'; $dkey = '{hash}' . __FUNCTION__; @@ -4147,7 +4147,7 @@ public function testDifferentTypeSortedSet() } public function testDifferentTypeHash() - { + { $key = '{hash}hash'; $dkey = '{hash}hash'; @@ -4176,7 +4176,7 @@ public function testDifferentTypeHash() $this->assertFalse($this->valkey_glide->lSet($key, 0, 'newValue')); $this->assertFalse($this->valkey_glide->lrem($key, 'lvalue', 1)); $this->assertFalse($this->valkey_glide->lPop($key)); - $this->assertFalse($this->valkey_glide->rPop($key)); + $this->assertFalse($this->valkey_glide->rPop($key)); // sets I/F $this->assertFalse($this->valkey_glide->sAdd($key, 'sValue1')); @@ -5165,31 +5165,52 @@ public function testGeoSearchStore() private function addTestCities() { // Clear any existing data - $this->valkey_glide->del('{geo}_test_key'); + $this->valkey_glide->del('{geo}_test_key'); $this->valkey_glide->del('{geo}_src_key'); $this->valkey_glide->del('{geo}_dst_key'); - + // Add cities with their coordinates to source key - $this->valkey_glide->geoadd('{geo}_src_key', - -121.837478, 39.728494, 'Chico', // Northern California - -121.494400, 38.581572, 'Sacramento', // Central California - -121.693583, 39.363777, 'Gridley', // Near Chico - -121.591355, 39.145725, 'Marysville', // Between Chico and Sacramento - -122.032182, 37.322998, 'Cupertino' // Bay Area + $this->valkey_glide->geoadd( + '{geo}_src_key', + -121.837478, + 39.728494, + 'Chico', // Northern California + -121.494400, + 38.581572, + 'Sacramento', // Central California + -121.693583, + 39.363777, + 'Gridley', // Near Chico + -121.591355, + 39.145725, + 'Marysville', // Between Chico and Sacramento + -122.032182, + 37.322998, + 'Cupertino' // Bay Area ); - + // Add cities with their coordinates - $this->valkey_glide->geoadd('{geo}_test_key', - -121.837478, 39.728494, 'Chico', // Northern California - -121.494400, 38.581572, 'Sacramento', // Central California - -121.693583, 39.363777, 'Gridley', // Near Chico - -121.591355, 39.145725, 'Marysville', // Between Chico and Sacramento - -122.032182, 37.322998, 'Cupertino' // Bay Area + $this->valkey_glide->geoadd( + '{geo}_test_key', + -121.837478, + 39.728494, + 'Chico', // Northern California + -121.494400, + 38.581572, + 'Sacramento', // Central California + -121.693583, + 39.363777, + 'Gridley', // Near Chico + -121.591355, + 39.145725, + 'Marysville', // Between Chico and Sacramento + -122.032182, + 37.322998, + 'Cupertino' // Bay Area ); - } - public function testGeoSearchStoreBasicRadius() + public function testGeoSearchStoreBasicRadius() { if (!$this->minVersionCheck('6.2.0')) { $this->markTestSkipped('GEOSEARCHSTORE requires Redis 6.2.0+'); @@ -5199,7 +5220,7 @@ public function testGeoSearchStoreBasicRadius() $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', 'Chico', 50, 'km'); $this->assertIsInt($count); $this->assertGTE(2, $count); // Should find at least Chico and Gridley - + // Verify the destination key was created and contains expected members $members = $this->valkey_glide->zrange('{geo}_dst_key', 0, -1); $this->assertIsArray($members); @@ -5214,11 +5235,16 @@ public function testGeoSearchStoreFromCoordinates() } $this->addTestCities(); // Test search and store from longitude/latitude coordinates - $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', - [-121.837478, 39.728494], 50, 'km'); + $count = $this->valkey_glide->geosearchstore( + '{geo}_dst_key', + '{geo}_src_key', + [-121.837478, 39.728494], + 50, + 'km' + ); $this->assertIsInt($count); $this->assertGTE(2, $count); - + // Verify stored results $members = $this->valkey_glide->zrange('{geo}_dst_key', 0, -1); $this->assertContains('Chico', $members); @@ -5232,11 +5258,16 @@ public function testGeoSearchStoreByBox() } $this->addTestCities(); // Test rectangular search and store (BYBOX) - $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', - 'Sacramento', [100, 100], 'km'); + $count = $this->valkey_glide->geosearchstore( + '{geo}_dst_key', + '{geo}_src_key', + 'Sacramento', + [100, 100], + 'km' + ); $this->assertIsInt($count); $this->assertGTE(1, $count); - + // Verify stored results $members = $this->valkey_glide->zrange('{geo}_dst_key', 0, -1); $this->assertContains('Sacramento', $members); @@ -5249,11 +5280,17 @@ public function testGeoSearchStoreWithCount() } $this->addTestCities(); // Test COUNT option - $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', - 'Sacramento', 200, 'km', ['count' => 2]); + $count = $this->valkey_glide->geosearchstore( + '{geo}_dst_key', + '{geo}_src_key', + 'Sacramento', + 200, + 'km', + ['count' => 2] + ); $this->assertIsInt($count); $this->assertLTE(2, $count); - + // Verify stored count matches returned count $storedCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals($count, $storedCount); @@ -5266,11 +5303,17 @@ public function testGeoSearchStoreWithCountAny() } $this->addTestCities(); // Test COUNT with ANY option - $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', - 'Sacramento', 200, 'km', ['count' => [3, 'ANY']]); + $count = $this->valkey_glide->geosearchstore( + '{geo}_dst_key', + '{geo}_src_key', + 'Sacramento', + 200, + 'km', + ['count' => [3, 'ANY']] + ); $this->assertIsInt($count); $this->assertLTE(3, $count); - + // Verify stored count $storedCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals($count, $storedCount); @@ -5283,11 +5326,17 @@ public function testGeoSearchStoreWithSort() } $this->addTestCities(); // Test ASC sorting - $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', - 'Sacramento', 200, 'km', ['sort' => 'ASC']); + $count = $this->valkey_glide->geosearchstore( + '{geo}_dst_key', + '{geo}_src_key', + 'Sacramento', + 200, + 'km', + ['sort' => 'ASC'] + ); $this->assertIsInt($count); $this->assertGTE(1, $count); - + // Verify results are stored $storedCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals($count, $storedCount); @@ -5300,15 +5349,21 @@ public function testGeoSearchStoreWithStoreDist() } $this->addTestCities(); // Test STOREDIST option - stores distances instead of geohashes - $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', - 'Chico', 50, 'km', ['storedist' => true]); + $count = $this->valkey_glide->geosearchstore( + '{geo}_dst_key', + '{geo}_src_key', + 'Chico', + 50, + 'km', + ['storedist' => true] + ); $this->assertIsInt($count); $this->assertGTE(2, $count); - + // Verify the destination key contains distance scores $membersWithScores = $this->valkey_glide->zrange('{geo}_dst_key', 0, -1, ['withscores' => true]); $this->assertIsArray($membersWithScores); - + // Check that scores are distances (should be reasonable values) foreach ($membersWithScores as $member => $score) { $this->assertIsString($member); @@ -5325,7 +5380,9 @@ public function testGeoSearchStoreComplexQuery() } $this->addTestCities(); // Complex query: box search from coordinates with multiple options - $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', + $count = $this->valkey_glide->geosearchstore( + '{geo}_dst_key', + '{geo}_src_key', [-121.5, 38.5], // coordinates [200, 200], // box dimensions 'km', @@ -5335,14 +5392,14 @@ public function testGeoSearchStoreComplexQuery() 'storedist' => true ] ); - + $this->assertIsInt($count); $this->assertLTE(3, $count); - + // Verify stored results $storedCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals($count, $storedCount); - + // Verify distances are stored as scores $membersWithScores = $this->valkey_glide->zrange('{geo}_dst_key', 0, -1, ['withscores' => true]); foreach ($membersWithScores as $member => $score) { @@ -5359,14 +5416,19 @@ public function testGeoSearchStoreDifferentUnits() $this->addTestCities(); // Test different units $units = ['m', 'km', 'ft', 'mi']; - + foreach ($units as $unit) { $dstKey = "{geo}_dst_key_$unit"; - $count = $this->valkey_glide->geosearchstore($dstKey, '{geo}_src_key', - 'Chico', 50000, $unit); + $count = $this->valkey_glide->geosearchstore( + $dstKey, + '{geo}_src_key', + 'Chico', + 50000, + $unit + ); $this->assertIsInt($count); $this->assertGTE(1, $count); - + // Clean up $this->valkey_glide->del($dstKey); } @@ -5379,19 +5441,29 @@ public function testGeoSearchStoreOverwriteDestination() } $this->addTestCities(); // First search and store - $count1 = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', - 'Chico', 30, 'km'); + $count1 = $this->valkey_glide->geosearchstore( + '{geo}_dst_key', + '{geo}_src_key', + 'Chico', + 30, + 'km' + ); $this->assertIsInt($count1); - + // Second search and store to same destination (should overwrite) - $count2 = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', - 'Sacramento', 100, 'km'); + $count2 = $this->valkey_glide->geosearchstore( + '{geo}_dst_key', + '{geo}_src_key', + 'Sacramento', + 100, + 'km' + ); $this->assertIsInt($count2); - + // Verify the destination was overwritten $finalCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals($count2, $finalCount); - + // Verify it contains Sacramento results, not Chico results $members = $this->valkey_glide->zrange('{geo}_dst_key', 0, -1); $this->assertContains('Sacramento', $members); @@ -5404,11 +5476,16 @@ public function testGeoSearchStoreEmptyResult() } $this->addTestCities(); // Search in area with no cities - $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', - [0, 0], 1, 'km'); + $count = $this->valkey_glide->geosearchstore( + '{geo}_dst_key', + '{geo}_src_key', + [0, 0], + 1, + 'km' + ); $this->assertIsInt($count); $this->assertEquals(0, $count); - + // Verify destination key is empty or doesn't exist $storedCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals(0, $storedCount); @@ -5421,11 +5498,16 @@ public function testGeoSearchStoreNonExistentSource() } $this->addTestCities(); // Search on non-existent source key - $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}non_existent_key', - 'member', 100, 'km'); + $count = $this->valkey_glide->geosearchstore( + '{geo}_dst_key', + '{geo}non_existent_key', + 'member', + 100, + 'km' + ); $this->assertIsInt($count); $this->assertEquals(0, $count); - + // Verify destination key is empty $storedCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals(0, $storedCount); @@ -5438,20 +5520,31 @@ public function testGeoSearchStoreReturnValue() } $this->addTestCities(); // Test that return value matches actual stored count - $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', - 'Sacramento', 200, 'km'); - + $count = $this->valkey_glide->geosearchstore( + '{geo}_dst_key', + '{geo}_src_key', + 'Sacramento', + 200, + 'km' + ); + $actualCount = $this->valkey_glide->zcard('{geo}_dst_key'); $this->assertEquals($count, $actualCount); - + // Test with COUNT limit - $limitedCount = $this->valkey_glide->geosearchstore('{geo}_dst_key2', '{geo}_src_key', - 'Sacramento', 200, 'km', ['count' => 2]); - + $limitedCount = $this->valkey_glide->geosearchstore( + '{geo}_dst_key2', + '{geo}_src_key', + 'Sacramento', + 200, + 'km', + ['count' => 2] + ); + $actualLimitedCount = $this->valkey_glide->zcard('{geo}_dst_key2'); $this->assertEquals($limitedCount, $actualLimitedCount); $this->assertLTE(2, $limitedCount); - + // Clean up $this->valkey_glide->del('{geo}_dst_key2'); } @@ -5463,15 +5556,20 @@ public function testGeoSearchStorePreservesGeoData() } $this->addTestCities(); // Store without STOREDIST (should preserve geo data) - $count = $this->valkey_glide->geosearchstore('{geo}_dst_key', '{geo}_src_key', - 'Chico', 50, 'km'); + $count = $this->valkey_glide->geosearchstore( + '{geo}_dst_key', + '{geo}_src_key', + 'Chico', + 50, + 'km' + ); $this->assertGTE(2, $count); - + // Verify we can perform geo operations on the stored data $distance = $this->valkey_glide->geodist('{geo}_dst_key', 'Chico', 'Gridley', 'km'); $this->assertIsFloat($distance); $this->assertGTE(0, $distance); - + // Verify geopos works on stored data $positions = $this->valkey_glide->geopos('{geo}_dst_key', 'Chico'); $this->assertIsArray($positions); @@ -5491,7 +5589,7 @@ public function testGeoSearchBasicRadius() $this->assertIsArray($result); $this->assertContains('Chico', $result); $this->assertContains('Gridley', $result); - + // Should not contain distant cities $this->assertFalse(in_array('Cupertino', $result)); } @@ -5516,10 +5614,10 @@ public function testGeoSearchByBox() } $this->addTestCities(); // Test rectangular search (BYBOX) - $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Sacramento', [150, 150], 'km'); + $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Sacramento', [150, 150], 'km'); $this->assertIsArray($result); $this->assertContains('Sacramento', $result); - + // Should contain cities within the box $this->assertContains('Marysville', $result); } @@ -5532,7 +5630,7 @@ public function testGeoSearchWithCoord() $this->addTestCities(); $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Chico', 50, 'km', ['withcoord']); $this->assertIsArray($result); - + foreach ($result as $city => $data) { $this->assertIsString($city); $this->assertIsArray($data); @@ -5552,7 +5650,7 @@ public function testGeoSearchWithDist() $this->addTestCities(); $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Chico', 50, 'km', ['withdist']); $this->assertIsArray($result); - + foreach ($result as $city => $data) { $this->assertIsString($city); $this->assertIsArray($data); @@ -5570,7 +5668,7 @@ public function testGeoSearchWithHash() $this->addTestCities(); $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Chico', 50, 'km', ['withhash']); $this->assertIsArray($result); - + foreach ($result as $city => $data) { $this->assertIsString($city); $this->assertIsArray($data); @@ -5585,10 +5683,15 @@ public function testGeoSearchWithAllOptions() $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); } $this->addTestCities(); - $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Chico', 50, 'km', - ['withdist', 'withhash', 'withcoord']); + $result = $this->valkey_glide->geosearch( + '{geo}_test_key', + 'Chico', + 50, + 'km', + ['withdist', 'withhash', 'withcoord'] + ); $this->assertIsArray($result); - + foreach ($result as $city => $data) { $this->assertIsString($city); $this->assertIsArray($data); @@ -5607,8 +5710,13 @@ public function testGeoSearchWithCount() } $this->addTestCities(); // Test COUNT option - $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Sacramento', 200, 'km', - ['count' => 2]); + $result = $this->valkey_glide->geosearch( + '{geo}_test_key', + 'Sacramento', + 200, + 'km', + ['count' => 2] + ); $this->assertIsArray($result); $this->assertLTE(2, count($result)); } @@ -5620,8 +5728,13 @@ public function testGeoSearchWithCountAny() } $this->addTestCities(); // Test COUNT with ANY option - $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Sacramento', 200, 'km', - ['count' => [2, 'ANY']]); + $result = $this->valkey_glide->geosearch( + '{geo}_test_key', + 'Sacramento', + 200, + 'km', + ['count' => [2, 'ANY']] + ); $this->assertIsArray($result); $this->assertLTE(2, count($result)); } @@ -5633,16 +5746,21 @@ public function testGeoSearchWithSortAsc() } $this->addTestCities(); // Test ASC sorting with distances - $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Sacramento', 200, 'km', - ['withdist', 'asc']); + $result = $this->valkey_glide->geosearch( + '{geo}_test_key', + 'Sacramento', + 200, + 'km', + ['withdist', 'asc'] + ); $this->assertIsArray($result); - + // Verify distances are in ascending order $distances = []; foreach ($result as $city => $data) { $distances[] = $data[0]; } - + $sortedDistances = $distances; sort($sortedDistances); $this->assertEquals($sortedDistances, $distances); @@ -5652,20 +5770,25 @@ public function testGeoSearchWithSortDesc() { if (!$this->minVersionCheck('6.2.0')) { $this->markTestSkipped('GEOSEARCH requires Redis 6.2.0+'); - } + } $this->addTestCities(); // Test DESC sorting with distances - $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Sacramento', 200, 'km', - ['withdist', 'desc']); + $result = $this->valkey_glide->geosearch( + '{geo}_test_key', + 'Sacramento', + 200, + 'km', + ['withdist', 'desc'] + ); $this->assertIsArray($result); - + // Verify distances are in descending order $distances = []; foreach ($result as $city => $data) { $distances[] = $data[0]; } - + $sortedDistances = $distances; rsort($sortedDistances); $this->assertEquals($sortedDistances, $distances); @@ -5678,16 +5801,21 @@ public function testGeoSearchAlternativeSortSyntax() } $this->addTestCities(); // Test alternative sort syntax using 'sort' key - $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Sacramento', 200, 'km', - ['withdist', 'sort' => 'ASC']); + $result = $this->valkey_glide->geosearch( + '{geo}_test_key', + 'Sacramento', + 200, + 'km', + ['withdist', 'sort' => 'ASC'] + ); $this->assertIsArray($result); - + // Verify distances are in ascending order $distances = []; foreach ($result as $city => $data) { $distances[] = $data[0]; } - + $sortedDistances = $distances; sort($sortedDistances); $this->assertEquals($sortedDistances, $distances); @@ -5701,7 +5829,7 @@ public function testGeoSearchDifferentUnits() $this->addTestCities(); // Test different units $units = ['m', 'km', 'ft', 'mi']; - + foreach ($units as $unit) { $result = $this->valkey_glide->geosearch('{geo}_test_key', 'Chico', 50000, $unit); $this->assertIsArray($result); @@ -5716,22 +5844,23 @@ public function testGeoSearchComplexQuery() } $this->addTestCities(); // Complex query: box search from coordinates with all options - $result = $this->valkey_glide->geosearch('{geo}_test_key', + $result = $this->valkey_glide->geosearch( + '{geo}_test_key', [-121.5, 38.5], // coordinates [200, 200], // box dimensions 'km', [ 'withdist', - 'withcoord', + 'withcoord', 'withhash', 'count' => [3, 'ANY'], 'sort' => 'ASC' ] ); - + $this->assertIsArray($result); $this->assertLTE(3, count($result)); - + foreach ($result as $city => $data) { $this->assertIsString($city); $this->assertIsArray($data); @@ -6252,103 +6381,102 @@ public function testXClaim() $this->markTestSkipped(); } foreach (['Pavlo', null] as $consumer) { - - foreach ([0, 100] as $min_idle_time) { - foreach ([false, true] as $justid) { - foreach ([0, 10] as $retrycount) { - /* We need to test not passing TIME/IDLE as well as passing either */ - if ($min_idle_time == 0) { - $topts = [[], ['IDLE', 1000000], ['TIME', time() * 1000]]; - } else { - $topts = [null]; - } - - foreach ($topts as $tinfo) { - if ($tinfo) { - list($ttype, $tvalue) = $tinfo; + foreach ([0, 100] as $min_idle_time) { + foreach ([false, true] as $justid) { + foreach ([0, 10] as $retrycount) { + /* We need to test not passing TIME/IDLE as well as passing either */ + if ($min_idle_time == 0) { + $topts = [[], ['IDLE', 1000000], ['TIME', time() * 1000]]; } else { - $ttype = null; - $tvalue = null; + $topts = [null]; } - /* Add some messages and create a group */ - $this->addStreamsAndGroups(['s'], 10, ['group1' => 0]); + foreach ($topts as $tinfo) { + if ($tinfo) { + list($ttype, $tvalue) = $tinfo; + } else { + $ttype = null; + $tvalue = null; + } - /* Create a second stream we can FORCE ownership on */ - $fids = $this->addStreamsAndGroups(['f'], 10, ['group1' => 0]); - $fids = $fids['f']; + /* Add some messages and create a group */ + $this->addStreamsAndGroups(['s'], 10, ['group1' => 0]); - /* Have consumer 'Mike' read the messages */ - $oids = $this->valkey_glide->xReadGroup('group1', 'Mike', ['s' => '>']); - $oids = array_keys($oids['s']); /* We're only dealing with stream 's' */ + /* Create a second stream we can FORCE ownership on */ + $fids = $this->addStreamsAndGroups(['f'], 10, ['group1' => 0]); + $fids = $fids['f']; - /* Construct our options array */ - $opts = []; - if ($justid) { - $opts[] = 'JUSTID'; - } - if ($retrycount) { - $opts['RETRYCOUNT'] = $retrycount; - } - if ($tvalue !== null) { - $opts[$ttype] = $tvalue; - } - - /* Now have pavlo XCLAIM them */ - $cids = $this->valkey_glide->xClaim('s', 'group1', 'Pavlo', $min_idle_time, $oids, $opts); - - if (! $justid) { - $cids = array_keys($cids); - } + /* Have consumer 'Mike' read the messages */ + $oids = $this->valkey_glide->xReadGroup('group1', 'Mike', ['s' => '>']); + $oids = array_keys($oids['s']); /* We're only dealing with stream 's' */ - if ($min_idle_time == 0) { - $this->assertEquals($cids, $oids); + /* Construct our options array */ + $opts = []; + if ($justid) { + $opts[] = 'JUSTID'; + } + if ($retrycount) { + $opts['RETRYCOUNT'] = $retrycount; + } + if ($tvalue !== null) { + $opts[$ttype] = $tvalue; + } - /* Append the FORCE option to our second stream where we have not already - * assigned to a PEL group */ - $opts[] = 'FORCE'; - $freturn = $this->valkey_glide->xClaim('f', 'group1', 'Test', 0, $fids, $opts); + /* Now have pavlo XCLAIM them */ + $cids = $this->valkey_glide->xClaim('s', 'group1', 'Pavlo', $min_idle_time, $oids, $opts); if (! $justid) { - $freturn = array_keys($freturn); + $cids = array_keys($cids); } - $this->assertEquals($freturn, $fids); + if ($min_idle_time == 0) { + $this->assertEquals($cids, $oids); - if ($retrycount || $tvalue !== null) { - $pending = null; - if ($consumer != null) { - $pending = $this->valkey_glide->xPending('s', 'group1', 0, '+', 1, $consumer); - } else { - $pending = $this->valkey_glide->xPending('s', 'group1', 0, '+', 1); - } - - if ($retrycount) { - $this->assertEquals($pending[0][3], $retrycount); + /* Append the FORCE option to our second stream where we have not already + * assigned to a PEL group */ + $opts[] = 'FORCE'; + $freturn = $this->valkey_glide->xClaim('f', 'group1', 'Test', 0, $fids, $opts); + + if (! $justid) { + $freturn = array_keys($freturn); } - if ($tvalue !== null) { - if ($ttype == 'IDLE') { - /* If testing IDLE the value must be >= what we set */ - $this->assertGTE($tvalue, $pending[0][2]); + + $this->assertEquals($freturn, $fids); + + if ($retrycount || $tvalue !== null) { + $pending = null; + if ($consumer != null) { + $pending = $this->valkey_glide->xPending('s', 'group1', 0, '+', 1, $consumer); } else { - /* Timing tests are notoriously irritating but I don't see - * how we'll get >= 20,000 ms between XCLAIM and XPENDING no - * matter how slow the machine/VM running the tests is */ - $this->assertLT(20000, $pending[0][2]); + $pending = $this->valkey_glide->xPending('s', 'group1', 0, '+', 1); + } + + if ($retrycount) { + $this->assertEquals($pending[0][3], $retrycount); + } + if ($tvalue !== null) { + if ($ttype == 'IDLE') { + /* If testing IDLE the value must be >= what we set */ + $this->assertGTE($tvalue, $pending[0][2]); + } else { + /* Timing tests are notoriously irritating but I don't see + * how we'll get >= 20,000 ms between XCLAIM and XPENDING no + * matter how slow the machine/VM running the tests is */ + $this->assertLT(20000, $pending[0][2]); + } } } + } else { + /* We're verifying that we get no messages when we've set 100 seconds + * as our idle time, which should match nothing */ + $this->assertEquals([], $cids); } - } else { - /* We're verifying that we get no messages when we've set 100 seconds - * as our idle time, which should match nothing */ - $this->assertEquals([], $cids); } } } } } } - } /* Make sure our XAUTOCLAIM handler works */ public function testXAutoClaim() From c9dd2df322c5da0b6474c5a2453d8084921d5d35 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 28 Sep 2025 15:43:44 +0300 Subject: [PATCH 20/46] updates --- tests/ValkeyGlideTest.php | 62 ++++++++++++++++++++++++++++++++++-- valkey_glide_commands_2.c | 5 --- valkey_glide_core_commands.c | 13 ++++---- valkey_glide_core_common.c | 2 ++ valkey_glide_list_common.c | 2 -- 5 files changed, 69 insertions(+), 15 deletions(-) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 8aba27c5..68f26b6f 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -878,7 +878,7 @@ public function testExpireOptions() $this->valkey_glide->del('eopts'); } - public function testExpiretime() + public function testExpiretime() { if (version_compare($this->version, '7.0.0') < 0) { $this->markTestSkipped(); @@ -905,7 +905,65 @@ public function testExpiretime() $this->assertEquals($now + 20, $this->valkey_glide->expiretime('key3')); $this->assertEquals($future_ms, $this->valkey_glide->pexpiretime('key3')); - $this->valkey_glide->del('key1', 'key2', 'key3'); + // Test PEXPIRE with options (Redis 7.0+) + + // PEXPIRE NX -- Set expiry only when the key has no expiry + $this->assertTrue($this->valkey_glide->set('pexpire_nx_key', 'value')); + $this->assertTrue($this->valkey_glide->pexpire('pexpire_nx_key', 10000, 'NX')); // Success - no expiry + $this->assertFalse($this->valkey_glide->pexpire('pexpire_nx_key', 15000, 'NX')); // Fail - has expiry + + // PEXPIRE XX -- Set expiry only when the key has an existing expiry + $this->assertTrue($this->valkey_glide->set('pexpire_xx_key', 'value')); + $this->assertFalse($this->valkey_glide->pexpire('pexpire_xx_key', 10000, 'XX')); // Fail - no expiry + $this->assertTrue($this->valkey_glide->pexpire('pexpire_xx_key', 10000)); // Set initial expiry + $this->assertTrue($this->valkey_glide->pexpire('pexpire_xx_key', 15000, 'XX')); // Success - has expiry + + // PEXPIRE GT -- Set expiry only when the new expiry is greater than current one + $this->assertTrue($this->valkey_glide->set('pexpire_gt_key', 'value')); + $this->assertTrue($this->valkey_glide->pexpire('pexpire_gt_key', 10000)); // Set initial expiry + $this->assertTrue($this->valkey_glide->pexpire('pexpire_gt_key', 15000, 'GT')); // Success - greater + $this->assertFalse($this->valkey_glide->pexpire('pexpire_gt_key', 12000, 'GT')); // Fail - not greater + + // PEXPIRE LT -- Set expiry only when the new expiry is less than current one + $this->assertTrue($this->valkey_glide->set('pexpire_lt_key', 'value')); + $this->assertTrue($this->valkey_glide->pexpire('pexpire_lt_key', 15000)); // Set initial expiry + $this->assertTrue($this->valkey_glide->pexpire('pexpire_lt_key', 10000, 'LT')); // Success - less + $this->assertFalse($this->valkey_glide->pexpire('pexpire_lt_key', 12000, 'LT')); // Fail - not less + + // Test PEXPIREAT with options (Redis 7.0+) + $now_ms = $now * 1000; + + // PEXPIREAT NX -- Set expiry only when the key has no expiry + $this->assertTrue($this->valkey_glide->set('pexpireat_nx_key', 'value')); + $this->assertTrue($this->valkey_glide->pexpireat('pexpireat_nx_key', $now_ms + 10000, 'NX')); // Success - no expiry + $this->assertFalse($this->valkey_glide->pexpireat('pexpireat_nx_key', $now_ms + 15000, 'NX')); // Fail - has expiry + + // PEXPIREAT XX -- Set expiry only when the key has an existing expiry + $this->assertTrue($this->valkey_glide->set('pexpireat_xx_key', 'value')); + $this->assertFalse($this->valkey_glide->pexpireat('pexpireat_xx_key', $now_ms + 10000, 'XX')); // Fail - no expiry + $this->assertTrue($this->valkey_glide->pexpireat('pexpireat_xx_key', $now_ms + 10000)); // Set initial expiry + $this->assertTrue($this->valkey_glide->pexpireat('pexpireat_xx_key', $now_ms + 15000, 'XX')); // Success - has expiry + + // PEXPIREAT GT -- Set expiry only when the new expiry is greater than current one + $this->assertTrue($this->valkey_glide->set('pexpireat_gt_key', 'value')); + $this->assertTrue($this->valkey_glide->pexpireat('pexpireat_gt_key', $now_ms + 10000)); // Set initial expiry + $this->assertTrue($this->valkey_glide->pexpireat('pexpireat_gt_key', $now_ms + 15000, 'GT')); // Success - greater + $this->assertFalse($this->valkey_glide->pexpireat('pexpireat_gt_key', $now_ms + 12000, 'GT')); // Fail - not greater + + // PEXPIREAT LT -- Set expiry only when the new expiry is less than current one + $this->assertTrue($this->valkey_glide->set('pexpireat_lt_key', 'value')); + $this->assertTrue($this->valkey_glide->pexpireat('pexpireat_lt_key', $now_ms + 15000)); // Set initial expiry + $this->assertTrue($this->valkey_glide->pexpireat('pexpireat_lt_key', $now_ms + 10000, 'LT')); // Success - less + $this->assertFalse($this->valkey_glide->pexpireat('pexpireat_lt_key', $now_ms + 12000, 'LT')); // Fail - not less + + // Verify expiry times are set correctly with options + $this->assertBetween($this->valkey_glide->pexpiretime('pexpire_nx_key'), $now_ms + 9000, $now_ms + 11000); + $this->assertBetween($this->valkey_glide->pexpiretime('pexpireat_gt_key'), $now_ms + 14000, $now_ms + 16000); + + // Clean up all test keys + $this->valkey_glide->del('key1', 'key2', 'key3', 'pexpire_nx_key', 'pexpire_xx_key', + 'pexpire_gt_key', 'pexpire_lt_key', 'pexpireat_nx_key', + 'pexpireat_xx_key', 'pexpireat_gt_key', 'pexpireat_lt_key'); } public function testGetEx() diff --git a/valkey_glide_commands_2.c b/valkey_glide_commands_2.c index a6d520f0..73237b69 100644 --- a/valkey_glide_commands_2.c +++ b/valkey_glide_commands_2.c @@ -526,9 +526,6 @@ int execute_mget_command(zval* object, int argc, zval* return_value, zend_class_ return 0; } - /* Execute the MGET command using the Glide client */ - array_init(return_value); - core_command_args_t args = {0}; args.glide_client = valkey_glide->glide_client; args.cmd_type = MGet; @@ -548,8 +545,6 @@ int execute_mget_command(zval* object, int argc, zval* return_value, zend_class_ /* Command succeeded, return_value is already set */ return 1; } else { - /* Command failed */ - zval_dtor(return_value); return 0; } } diff --git a/valkey_glide_core_commands.c b/valkey_glide_core_commands.c index d120f734..0deb6900 100644 --- a/valkey_glide_core_commands.c +++ b/valkey_glide_core_commands.c @@ -2059,6 +2059,8 @@ int execute_lcs_command(zval* object, int argc, zval* return_value, zend_class_e arg_count++; } + char minmatchlen_str[32]; + /* Add MINMATCHLEN option if specified */ if (has_minmatchlen) { args[arg_count] = (uintptr_t) "MINMATCHLEN"; @@ -2067,12 +2069,11 @@ int execute_lcs_command(zval* object, int argc, zval* return_value, zend_class_e /* Add the minmatchlen value */ size_t minmatchlen_len; - char* minmatchlen_str = long_to_string(minmatchlen_value, &minmatchlen_len); - if (!minmatchlen_str) { - return 0; - } - args[arg_count] = (uintptr_t) minmatchlen_str; - args_len[arg_count] = minmatchlen_len; + minmatchlen_len = + snprintf(minmatchlen_str, sizeof(minmatchlen_str), "%ld", minmatchlen_value); + minmatchlen_str[minmatchlen_len] = '\0'; + args[arg_count] = (uintptr_t) minmatchlen_str; + args_len[arg_count] = minmatchlen_len; arg_count++; } diff --git a/valkey_glide_core_common.c b/valkey_glide_core_common.c index 67cb77c6..5bd51b91 100644 --- a/valkey_glide_core_common.c +++ b/valkey_glide_core_common.c @@ -102,12 +102,14 @@ int execute_core_command(valkey_glide_object* valkey_glide, /* Non-routed commands use standard processor */ res = processor(result->response, result_ptr, return_value); } else { + efree(result_ptr); ZVAL_FALSE(return_value); } /* Free the result - handle_string_response doesn't free it */ free_command_result(result); } else { + efree(result_ptr); ZVAL_FALSE(return_value); } diff --git a/valkey_glide_list_common.c b/valkey_glide_list_common.c index 1655e582..c9374811 100644 --- a/valkey_glide_list_common.c +++ b/valkey_glide_list_common.c @@ -96,8 +96,6 @@ int process_list_string_result_async(CommandResponse* response, void* output, zv * Batch-compatible wrapper for array responses */ int process_list_array_result_async(CommandResponse* response, void* output, zval* return_value) { - array_init(return_value); - return command_response_to_zval( response, return_value, COMMAND_RESPONSE_NOT_ASSOSIATIVE, false); } From bd9e1210a424ba4f54ef9844136c8acf8b28882e Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 28 Sep 2025 17:29:59 +0300 Subject: [PATCH 21/46] fix memory leak --- valkey_glide_core_commands.c | 1 + valkey_glide_core_common.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/valkey_glide_core_commands.c b/valkey_glide_core_commands.c index 0deb6900..51225c0a 100644 --- a/valkey_glide_core_commands.c +++ b/valkey_glide_core_commands.c @@ -1554,6 +1554,7 @@ int execute_del_array(const void* glide_client, free_command_result(cmd_result); } } + zval_ptr_dtor(&keys_array); free_core_args(cmd_args, cmd_args_len, allocated_strings, allocated_count); return result; } diff --git a/valkey_glide_core_common.c b/valkey_glide_core_common.c index 5bd51b91..a7af577d 100644 --- a/valkey_glide_core_common.c +++ b/valkey_glide_core_common.c @@ -1601,6 +1601,8 @@ int execute_multi_key_command(valkey_glide_object* valkey_glide, ZVAL_COPY(return_value, object); /* return_value should already contain $this */ } + + zval_ptr_dtor(&temp_array); return result; } else { /* Invalid input - neither single string, array, nor multiple strings */ From a7eead6f8320a6838843904dd9c2510d7578da1a Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 28 Sep 2025 18:05:45 +0300 Subject: [PATCH 22/46] fix memory leak --- tests/ValkeyGlideTest.php | 10 +++++----- valkey_glide_core_commands.c | 10 +++------- valkey_glide_core_common.c | 1 + valkey_glide_core_common.h | 3 +-- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 68f26b6f..a2377c0c 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -878,7 +878,7 @@ public function testExpireOptions() $this->valkey_glide->del('eopts'); } - public function testExpiretime() + public function testExpiretime() { if (version_compare($this->version, '7.0.0') < 0) { $this->markTestSkipped(); @@ -957,8 +957,8 @@ public function testExpiretime() $this->assertFalse($this->valkey_glide->pexpireat('pexpireat_lt_key', $now_ms + 12000, 'LT')); // Fail - not less // Verify expiry times are set correctly with options - $this->assertBetween($this->valkey_glide->pexpiretime('pexpire_nx_key'), $now_ms + 9000, $now_ms + 11000); - $this->assertBetween($this->valkey_glide->pexpiretime('pexpireat_gt_key'), $now_ms + 14000, $now_ms + 16000); + $this->assertBetween($this->valkey_glide->pexpiretime('pexpire_nx_key'), $now_ms + 8000, $now_ms + 12000); + $this->assertBetween($this->valkey_glide->pexpiretime('pexpireat_gt_key'), $now_ms + 12000, $now_ms + 16000); // Clean up all test keys $this->valkey_glide->del('key1', 'key2', 'key3', 'pexpire_nx_key', 'pexpire_xx_key', @@ -1684,7 +1684,7 @@ public function testlMove() public function testMove() { // Version check if needed (move has been available since early Redis versions) - + $this->valkey_glide->flushAll(); //TODO remove once the select is supported again. $key1 = 'move_test_key1'; $key2 = 'move_test_key2'; $value1 = 'test_value1'; @@ -1692,7 +1692,7 @@ public function testMove() // Ensure we're in database 0 // $this->valkey_glide->select(0); - + // Clean up any existing keys $this->valkey_glide->del($key1, $key2); // $this->valkey_glide->select(1); diff --git a/valkey_glide_core_commands.c b/valkey_glide_core_commands.c index 51225c0a..2848d1d8 100644 --- a/valkey_glide_core_commands.c +++ b/valkey_glide_core_commands.c @@ -1518,13 +1518,9 @@ int execute_del_array(const void* glide_client, /* Create temporary zval array from HashTable */ zval keys_array; - array_init(&keys_array); - - zval* key; - ZEND_HASH_FOREACH_VAL(keys_hash, key) { - add_next_index_zval(&keys_array, key); - } - ZEND_HASH_FOREACH_END(); + ZVAL_ARR(&keys_array, keys_hash); + /* Bump refcount so it stays alive while you pass it down */ + GC_TRY_ADDREF(keys_hash); /* Use core framework with converted array */ core_command_args_t args = {0}; diff --git a/valkey_glide_core_common.c b/valkey_glide_core_common.c index a7af577d..7a20306f 100644 --- a/valkey_glide_core_common.c +++ b/valkey_glide_core_common.c @@ -1583,6 +1583,7 @@ int execute_multi_key_command(valkey_glide_object* valkey_glide, array_init(&temp_array); for (int i = 0; i < keys_count; i++) { + Z_TRY_ADDREF_P(&keys[i]); add_next_index_zval(&temp_array, &keys[i]); } diff --git a/valkey_glide_core_common.h b/valkey_glide_core_common.h index 2f53ff1b..be5b4692 100644 --- a/valkey_glide_core_common.h +++ b/valkey_glide_core_common.h @@ -30,8 +30,7 @@ typedef enum { CORE_ARG_TYPE_STRING, CORE_ARG_TYPE_LONG, CORE_ARG_TYPE_DOUBLE, - CORE_ARG_TYPE_ARRAY, - CORE_ARG_TYPE_KEY_VALUE_PAIRS + CORE_ARG_TYPE_ARRAY } core_arg_type_t; /* Flexible argument container */ From 5af9895182244d6b9acfa2a008f9175625bca3a2 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 28 Sep 2025 18:31:36 +0300 Subject: [PATCH 23/46] more leaks fixes --- valkey_glide_commands_3.c | 18 +++++++++--------- valkey_glide_core_commands.c | 10 +++------- valkey_glide_hash_common.c | 6 +++++- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/valkey_glide_commands_3.c b/valkey_glide_commands_3.c index 9bde1d12..85231225 100644 --- a/valkey_glide_commands_3.c +++ b/valkey_glide_commands_3.c @@ -560,16 +560,13 @@ int execute_function_command(zval* object, int argc, zval* return_value, zend_cl } } - enum RequestType* output = emalloc(sizeof(enum RequestType)); - *output = function_command_type; - /* Buffer the command for batch execution */ int buffer_result = buffer_command_for_batch(valkey_glide, function_command_type, batch_args, arg_lengths, final_arg_count, - output, + NULL, process_function_command_reposonse); /* Free the argument arrays */ @@ -1262,6 +1259,7 @@ static int process_config_command_respose(CommandResponse* response, status = 0; } } + efree(output); return status; } @@ -1451,8 +1449,8 @@ int execute_config_command(zval* object, int argc, zval* return_value, zend_clas } if (valkey_glide->is_in_batch_mode) { - enum RequestType* output = emalloc(sizeof(enum RequestType)); - *output = command_type; + enum RequestType* command_type_ptr = emalloc(sizeof(enum RequestType)); + *command_type_ptr = command_type; /* In batch mode, buffer the command and return $this for method chaining */ if (buffer_command_for_batch(valkey_glide, command_type, @@ -1460,7 +1458,7 @@ int execute_config_command(zval* object, int argc, zval* return_value, zend_clas args_len, arg_count, - output, + command_type_ptr, process_config_command_respose)) { /* Return $this */ ZVAL_COPY(return_value, object); @@ -1485,8 +1483,10 @@ int execute_config_command(zval* object, int argc, zval* return_value, zend_clas } if (result->response) { - status = process_config_command_respose( - result->response, &command_type, return_value); + enum RequestType* command_type_ptr = emalloc(sizeof(enum RequestType)); + *command_type_ptr = command_type; + status = process_config_command_respose( + result->response, command_type_ptr, return_value); } free_command_result(result); } diff --git a/valkey_glide_core_commands.c b/valkey_glide_core_commands.c index 2848d1d8..414cf6fd 100644 --- a/valkey_glide_core_commands.c +++ b/valkey_glide_core_commands.c @@ -1606,13 +1606,8 @@ int execute_unlink_array(const void* glide_client, /* Create temporary zval array from HashTable */ zval keys_array; - array_init(&keys_array); - - zval* key; - ZEND_HASH_FOREACH_VAL(keys_hash, key) { - add_next_index_zval(&keys_array, key); - } - ZEND_HASH_FOREACH_END(); + ZVAL_ARR(&keys_array, keys_hash); + GC_TRY_ADDREF(keys_hash); // keep alive /* Use core framework with converted array */ core_command_args_t args = {0}; @@ -1642,6 +1637,7 @@ int execute_unlink_array(const void* glide_client, free_command_result(cmd_result); } } + zval_ptr_dtor(&keys_array); free_core_args(cmd_args, cmd_args_len, allocated_strings, allocated_count); return result; } diff --git a/valkey_glide_hash_common.c b/valkey_glide_hash_common.c index 63602e4b..5223b9b7 100644 --- a/valkey_glide_hash_common.c +++ b/valkey_glide_hash_common.c @@ -89,7 +89,7 @@ int execute_h_generic_command(valkey_glide_object* valkey_glide, args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; default: - return 0; + goto cleanup; } if (arg_count <= 0) { @@ -119,6 +119,9 @@ int execute_h_generic_command(valkey_glide_object* valkey_glide, cleanup: /* Clean up allocated resources */ cleanup_h_command_args(allocated_strings, allocated_count, cmd_args, args_len); + if (result_ptr) { + efree(result_ptr); + } return status; } @@ -646,6 +649,7 @@ int process_h_mget_result(CommandResponse* response, void* output, zval* return_ /* Check if the command was successful */ if (!response) { + efree(args); return 0; } /* Initialize return array */ From 64c26d254f6ba18a06f15735eb6c18e921112e85 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 28 Sep 2025 18:35:53 +0300 Subject: [PATCH 24/46] updates --- valkey_glide_hash_common.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/valkey_glide_hash_common.c b/valkey_glide_hash_common.c index 5223b9b7..55b230de 100644 --- a/valkey_glide_hash_common.c +++ b/valkey_glide_hash_common.c @@ -89,7 +89,10 @@ int execute_h_generic_command(valkey_glide_object* valkey_glide, args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; default: - goto cleanup; + if (result_ptr) { + efree(result_ptr); + } + return 0; } if (arg_count <= 0) { @@ -112,16 +115,22 @@ int execute_h_generic_command(valkey_glide_object* valkey_glide, if (result) { if (!result->command_error && result->response && process_result) { status = process_result(result->response, result_ptr, return_value); + } else { + if (result_ptr) { + efree(result_ptr); + } } free_command_result(result); + } else { + if (result_ptr) { + efree(result_ptr); + } } cleanup: /* Clean up allocated resources */ cleanup_h_command_args(allocated_strings, allocated_count, cmd_args, args_len); - if (result_ptr) { - efree(result_ptr); - } + return status; } From 66175334daa1ae2546254c89c33fb4116727e696 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Mon, 29 Sep 2025 13:10:16 +0300 Subject: [PATCH 25/46] fix memory leak --- command_response.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/command_response.c b/command_response.c index 42c0e0c8..320d9c34 100644 --- a/command_response.c +++ b/command_response.c @@ -519,7 +519,6 @@ int command_response_to_zval(CommandResponse* response, } } } else if (use_associative_array == COMMAND_RESPONSE_ARRAY_ASSOCIATIVE) { - zval field, value; #if DEBUG_COMMAND_RESPONSE_TO_ZVAL printf("%s:%d - response->array_value[0]->command_response_type = %d\n ", __FILE__, @@ -528,6 +527,7 @@ int command_response_to_zval(CommandResponse* response, #endif array_init(output); for (int64_t i = 0; i < response->array_value_len; ++i) { + zval field, value; command_response_to_zval(&response->array_value[i], &field, COMMAND_RESPONSE_NOT_ASSOSIATIVE, @@ -542,8 +542,11 @@ int command_response_to_zval(CommandResponse* response, val_zval = zend_hash_index_find(Z_ARRVAL(field), 1); // field[1] if (key_zval && val_zval && Z_TYPE_P(key_zval) == IS_STRING) { - add_assoc_zval(output, Z_STRVAL_P(key_zval), val_zval); + zend_string* key = Z_STR_P(key_zval); + GC_TRY_ADDREF(Z_STR_P(val_zval)); + zend_hash_update(Z_ARRVAL_P(output), key, val_zval); } + zval_ptr_dtor(&field); } } } else { From 091194f3035c7996086d4f662388a9424a08b8b3 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Mon, 29 Sep 2025 13:52:56 +0300 Subject: [PATCH 26/46] fix memory leaks --- command_response.c | 5 +++-- tests/ValkeyGlideTest.php | 2 +- valkey_glide_core_commands.c | 14 ++++++++++++-- valkey_glide_z_common.c | 6 ++---- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/command_response.c b/command_response.c index 320d9c34..5379a490 100644 --- a/command_response.c +++ b/command_response.c @@ -730,13 +730,14 @@ int command_response_to_stream_zval(CommandResponse* response, zval* output) { // (int)stream_id_len, stream_id); /* Create associative array for field-value pairs */ - zval field_array; - array_init(&field_array); + // printf("%s:%d - DEBUG: Processing stream ID: %.*s, // element->map_value->response_type = %d\n", __FILE__, __LINE__, // (int)stream_id_len, stream_id, element->map_value->response_type); /* Process nested field-value pairs - add safety check */ if (element->map_value->response_type == Array) { + zval field_array; + array_init(&field_array); /* Safe version that checks array bounds */ if (element->map_value->array_value_len > 0) { // printf("%s:%d - DEBUG: Processing Array response for stream ID: %.*s, diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index a2377c0c..522b90f6 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -3608,7 +3608,7 @@ public function testZRandMember() $this->valkey_glide->del('key'); $this->valkey_glide->zAdd('key', 0, 'a', 1, 'b', 2, 'c', 3, 'd', 4, 'e'); - $result = $this->valkey_glide->zRandMember('key'); + $result = $this->valkey_glide->zRandMember('key'); $this->assertEquals(array_intersect($result, ['a', 'b', 'c', 'd', 'e']), $result); diff --git a/valkey_glide_core_commands.c b/valkey_glide_core_commands.c index 414cf6fd..ecf8fceb 100644 --- a/valkey_glide_core_commands.c +++ b/valkey_glide_core_commands.c @@ -1208,6 +1208,9 @@ int execute_info_command(zval* object, int argc, zval* return_value, zend_class_ process_info_result); /* Free the argument arrays */ + for (int i = 0; i < section_count; i++) { + efree((char*) (cmd_args[i])); + } if (cmd_args) efree(cmd_args); @@ -1247,6 +1250,10 @@ int execute_info_command(zval* object, int argc, zval* return_value, zend_class_ cmd_args_len, &args[0]); /* Route parameter */ + for (int i = 0; i < section_count; i++) { + efree((char*) (cmd_args[i])); + } + /* Free the argument arrays */ if (cmd_args) efree(cmd_args); @@ -1266,6 +1273,9 @@ int execute_info_command(zval* object, int argc, zval* return_value, zend_class_ cmd_result = execute_command( valkey_glide->glide_client, Info, processed_args, cmd_args, cmd_args_len); + for (int i = 0; i < args_count; i++) { + efree((char*) (cmd_args[i])); + } /* Free the argument arrays */ if (cmd_args) efree(cmd_args); @@ -1978,8 +1988,8 @@ int execute_lcs_command(zval* object, int argc, zval* return_value, zend_class_e /* Prepare command arguments */ unsigned long arg_count = 2; /* key1 + key2 */ - uintptr_t - args[7]; /* Maximum 7 arguments: key1, key2, LEN, IDX, MINMATCHLEN, value, WITHMATCHLEN */ + uintptr_t args[7]; /* Maximum 7 arguments: key1, key2, LEN, IDX, MINMATCHLEN, value, + WITHMATCHLEN */ unsigned long args_len[7]; /* First argument: key1 */ diff --git a/valkey_glide_z_common.c b/valkey_glide_z_common.c index cb0a8577..065386ca 100644 --- a/valkey_glide_z_common.c +++ b/valkey_glide_z_common.c @@ -1804,15 +1804,13 @@ int process_z_array_zrand_result(CommandResponse* response, void* output, zval* if (Z_TYPE_P(return_value) == IS_STRING) { // Save the string temporarily - zval tmp; - - ZVAL_COPY(&tmp, return_value); + zend_string* str = Z_STR_P(return_value); // Convert return_value to an array array_init(return_value); // Add the original string as the first element (index 0) - add_next_index_zval(return_value, &tmp); + add_next_index_str(return_value, str); } if (array_data->withscores && success && Z_TYPE_P(return_value) == IS_ARRAY) { From 3a196a9e7f6550d120826227985f69b6083c7419 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Mon, 29 Sep 2025 14:34:40 +0300 Subject: [PATCH 27/46] updates --- command_response.c | 1 + valkey_glide_commands.c | 16 +++++++--------- valkey_glide_hash_common.c | 10 ++++++++++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/command_response.c b/command_response.c index 5379a490..e3cd8255 100644 --- a/command_response.c +++ b/command_response.c @@ -634,6 +634,7 @@ int command_response_to_zval(CommandResponse* response, } else { // Add the key as a separate array element (original behavior) add_next_index_zval(output, &key); + // zval_dtor(&key); // Clean up the key since we're using it as an index // Add the value as the next array element add_next_index_zval(output, &value); } diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index 9f632dd1..27ab22a9 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -486,17 +486,16 @@ int execute_object_command_impl(valkey_glide_object* valkey_glide, } /* For HELP and other subcommands, use CustomCommand (default) */ - char* subcommand_copy = emalloc(subcommand_len + 1); - if (!subcommand_copy) { - return -1; - } - memcpy(subcommand_copy, subcommand, subcommand_len); - subcommand_copy[subcommand_len] = '\0'; /* Check for batch mode */ if (valkey_glide->is_in_batch_mode) { /* Create a copy of subcommand for the callback */ - + char* subcommand_copy = emalloc(subcommand_len + 1); + if (!subcommand_copy) { + return -1; + } + memcpy(subcommand_copy, subcommand, subcommand_len); + subcommand_copy[subcommand_len] = '\0'; /* Buffer command for batch execution */ int result = buffer_command_for_batch( @@ -516,7 +515,6 @@ int execute_object_command_impl(valkey_glide_object* valkey_glide, CommandResult* result = execute_command(valkey_glide->glide_client, req_type, 1, args, args_len); if (result == NULL) { - efree(subcommand_copy); return -1; } @@ -524,7 +522,7 @@ int execute_object_command_impl(valkey_glide_object* valkey_glide, int ret_val = -1; /* Default to error */ if (result->response) { ret_val = process_object_command_result( - result->response, subcommand_copy, subcommand_len, return_value); + result->response, subcommand, subcommand_len, return_value); } /* Clean up */ diff --git a/valkey_glide_hash_common.c b/valkey_glide_hash_common.c index 55b230de..62049c9f 100644 --- a/valkey_glide_hash_common.c +++ b/valkey_glide_hash_common.c @@ -89,13 +89,20 @@ int execute_h_generic_command(valkey_glide_object* valkey_glide, args, &cmd_args, &args_len, &allocated_strings, &allocated_count); break; default: + if (result_ptr) { + efree(args->fields); efree(result_ptr); } + return 0; } if (arg_count <= 0) { + if (result_ptr) { + efree(args->fields); + efree(result_ptr); + } goto cleanup; } @@ -117,12 +124,14 @@ int execute_h_generic_command(valkey_glide_object* valkey_glide, status = process_result(result->response, result_ptr, return_value); } else { if (result_ptr) { + efree(args->fields); efree(result_ptr); } } free_command_result(result); } else { if (result_ptr) { + efree(args->fields); efree(result_ptr); } } @@ -658,6 +667,7 @@ int process_h_mget_result(CommandResponse* response, void* output, zval* return_ /* Check if the command was successful */ if (!response) { + efree(args->fields); efree(args); return 0; } From 142a7213730f0d30f2d5f2523f63829822819699 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Mon, 29 Sep 2025 17:09:23 +0300 Subject: [PATCH 28/46] fix memory leak --- command_response.c | 1 - valkey_glide_core_commands.c | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/command_response.c b/command_response.c index e3cd8255..5379a490 100644 --- a/command_response.c +++ b/command_response.c @@ -634,7 +634,6 @@ int command_response_to_zval(CommandResponse* response, } else { // Add the key as a separate array element (original behavior) add_next_index_zval(output, &key); - // zval_dtor(&key); // Clean up the key since we're using it as an index // Add the value as the next array element add_next_index_zval(output, &value); } diff --git a/valkey_glide_core_commands.c b/valkey_glide_core_commands.c index ecf8fceb..242bec9b 100644 --- a/valkey_glide_core_commands.c +++ b/valkey_glide_core_commands.c @@ -1150,6 +1150,7 @@ int process_info_result(CommandResponse* response, void* output, zval* return_va } } ZEND_HASH_FOREACH_END(); + zval_ptr_dtor(&temp_result); return 1; } return 0; From 063662801b3f3c355e71e95a622479490082ae8d Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 19 Oct 2025 17:19:50 +0300 Subject: [PATCH 29/46] updates --- tests/ValkeyGlideTest.php | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 522b90f6..e5edbb0f 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -906,7 +906,7 @@ public function testExpiretime() $this->assertEquals($future_ms, $this->valkey_glide->pexpiretime('key3')); // Test PEXPIRE with options (Redis 7.0+) - + // PEXPIRE NX -- Set expiry only when the key has no expiry $this->assertTrue($this->valkey_glide->set('pexpire_nx_key', 'value')); $this->assertTrue($this->valkey_glide->pexpire('pexpire_nx_key', 10000, 'NX')); // Success - no expiry @@ -961,9 +961,19 @@ public function testExpiretime() $this->assertBetween($this->valkey_glide->pexpiretime('pexpireat_gt_key'), $now_ms + 12000, $now_ms + 16000); // Clean up all test keys - $this->valkey_glide->del('key1', 'key2', 'key3', 'pexpire_nx_key', 'pexpire_xx_key', - 'pexpire_gt_key', 'pexpire_lt_key', 'pexpireat_nx_key', - 'pexpireat_xx_key', 'pexpireat_gt_key', 'pexpireat_lt_key'); + $this->valkey_glide->del( + 'key1', + 'key2', + 'key3', + 'pexpire_nx_key', + 'pexpire_xx_key', + 'pexpire_gt_key', + 'pexpire_lt_key', + 'pexpireat_nx_key', + 'pexpireat_xx_key', + 'pexpireat_gt_key', + 'pexpireat_lt_key' + ); } public function testGetEx() @@ -1692,7 +1702,7 @@ public function testMove() // Ensure we're in database 0 // $this->valkey_glide->select(0); - + // Clean up any existing keys $this->valkey_glide->del($key1, $key2); // $this->valkey_glide->select(1); @@ -3608,7 +3618,7 @@ public function testZRandMember() $this->valkey_glide->del('key'); $this->valkey_glide->zAdd('key', 0, 'a', 1, 'b', 2, 'c', 3, 'd', 4, 'e'); - $result = $this->valkey_glide->zRandMember('key'); + $result = $this->valkey_glide->zRandMember('key'); $this->assertEquals(array_intersect($result, ['a', 'b', 'c', 'd', 'e']), $result); From 0abe7413743ba13ddcfe6b6a42a1ff261bd8b588 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Sun, 19 Oct 2025 17:45:14 +0300 Subject: [PATCH 30/46] iupdates --- valkey-glide | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valkey-glide b/valkey-glide index b549eff0..81079af7 160000 --- a/valkey-glide +++ b/valkey-glide @@ -1 +1 @@ -Subproject commit b549eff0340f693ecfe86f01a8a1a112098c8a98 +Subproject commit 81079af701e6d1d24028d4414736709fc342acd1 From 77d8089ee320c156e4b20ce992b7cd8145711eca Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Mon, 20 Oct 2025 14:16:34 +0300 Subject: [PATCH 31/46] free memory leak --- valkey_glide_s_common.c | 71 ++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/valkey_glide_s_common.c b/valkey_glide_s_common.c index 0e55bdde..33154c15 100644 --- a/valkey_glide_s_common.c +++ b/valkey_glide_s_common.c @@ -633,14 +633,6 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r } /* Handle scan completion: when server returns cursor="0", scan is complete */ if (cursor_resp->string_value_len == 1 && cursor_resp->string_value[0] == '0') { - /* Free old cursor and keep it as "0" to indicate completion */ - if (args->cursor) { - efree(args->cursor); - } - args->cursor = emalloc(2); - strcpy(args->cursor, "0"); - - /* If there are elements in this final batch, return them using robust conversion */ if (elements_resp->array_value_len > 0) { status = command_response_to_zval(elements_resp, @@ -650,9 +642,16 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r : COMMAND_RESPONSE_NOT_ASSOSIATIVE, false); if (args->scan_iter) { - ZVAL_STRING(args->scan_iter, args->cursor); + ZVAL_STRING(args->scan_iter, "0"); efree(args->cursor); efree(args); + } else { + /* Free old cursor and keep it as "0" to indicate completion */ + if (args->cursor) { + efree(args->cursor); + } + args->cursor = emalloc(2); + strcpy(args->cursor, "0"); } return status; @@ -665,7 +664,12 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r efree(args->cursor); efree(args); } else { - args->cursor = "0"; + /* Free old cursor and keep it as "0" to indicate completion */ + if (args->cursor) { + efree(args->cursor); + } + args->cursor = emalloc(2); + strcpy(args->cursor, "0"); } return 1; @@ -673,30 +677,37 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r } /* Normal case: cursor != "0", update cursor string and return elements array */ - if (args->cursor) { - efree(args->cursor); - } - - /* Use length-controlled string copying to prevent reading beyond string boundary */ - size_t cursor_len = cursor_resp->string_value_len; - args->cursor = emalloc(cursor_len + 1); - memcpy(args->cursor, new_cursor_str, cursor_len); - (args->cursor)[cursor_len] = '\0'; - - - /* Use command_response_to_zval for robust element processing */ - status = command_response_to_zval(elements_resp, - return_value, - (args->cmd_type == HScan || args->cmd_type == ZScan) - ? COMMAND_RESPONSE_SCAN_ASSOSIATIVE_ARRAY - : COMMAND_RESPONSE_NOT_ASSOSIATIVE, - false); if (args->scan_iter) { - ZVAL_STRING(args->scan_iter, args->cursor); + /* For scan_iter mode, we'll update the zval directly and free everything */ + status = command_response_to_zval(elements_resp, + return_value, + (args->cmd_type == HScan || args->cmd_type == ZScan) + ? COMMAND_RESPONSE_SCAN_ASSOSIATIVE_ARRAY + : COMMAND_RESPONSE_NOT_ASSOSIATIVE, + false); + ZVAL_STRING(args->scan_iter, new_cursor_str); efree(args->cursor); efree(args); - } + } else { + /* For non-scan_iter mode, update the cursor pointer */ + if (args->cursor) { + efree(args->cursor); + } + /* Use length-controlled string copying to prevent reading beyond string boundary */ + size_t cursor_len = cursor_resp->string_value_len; + args->cursor = emalloc(cursor_len + 1); + memcpy(args->cursor, new_cursor_str, cursor_len); + (args->cursor)[cursor_len] = '\0'; + + /* Use command_response_to_zval for robust element processing */ + status = command_response_to_zval(elements_resp, + return_value, + (args->cmd_type == HScan || args->cmd_type == ZScan) + ? COMMAND_RESPONSE_SCAN_ASSOSIATIVE_ARRAY + : COMMAND_RESPONSE_NOT_ASSOSIATIVE, + false); + } return status; } From e3348c27b6933b51effa8fd3a94c87b3cf28794d Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Tue, 21 Oct 2025 08:45:57 +0300 Subject: [PATCH 32/46] fixes --- valkey_glide_s_common.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/valkey_glide_s_common.c b/valkey_glide_s_common.c index 33154c15..58de46be 100644 --- a/valkey_glide_s_common.c +++ b/valkey_glide_s_common.c @@ -573,7 +573,7 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r if (!response) { if (args->scan_iter) { - ZVAL_STRING(args->scan_iter, "0"); + ZVAL_STRINGL(args->scan_iter, "0", 1); efree(args->cursor); efree(args); } else { @@ -587,7 +587,7 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r if (response->response_type != Array || response->array_value_len < 2) { if (args->scan_iter) { - ZVAL_STRING(args->scan_iter, "0"); + ZVAL_STRINGL(args->scan_iter, "0", 1); efree(args->cursor); efree(args); } else { @@ -607,7 +607,7 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r /* Handle unexpected cursor type */ if (args->scan_iter) { - ZVAL_STRING(args->scan_iter, "0"); + ZVAL_STRINGL(args->scan_iter, "0", 1); efree(args->cursor); efree(args); } else { @@ -622,7 +622,7 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r if (elements_resp->response_type != Array) { array_init(return_value); if (args->scan_iter) { - ZVAL_STRING(args->scan_iter, "0"); + ZVAL_STRINGL(args->scan_iter, "0", 1); efree(args->cursor); efree(args); } else { @@ -642,7 +642,7 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r : COMMAND_RESPONSE_NOT_ASSOSIATIVE, false); if (args->scan_iter) { - ZVAL_STRING(args->scan_iter, "0"); + ZVAL_STRINGL(args->scan_iter, "0", 1); efree(args->cursor); efree(args); } else { @@ -660,7 +660,7 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r /* No elements in final batch - return FALSE to terminate loop */ array_init(return_value); if (args->scan_iter) { - ZVAL_STRING(args->scan_iter, "0"); + ZVAL_STRINGL(args->scan_iter, "0", 1); efree(args->cursor); efree(args); } else { @@ -685,7 +685,7 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r ? COMMAND_RESPONSE_SCAN_ASSOSIATIVE_ARRAY : COMMAND_RESPONSE_NOT_ASSOSIATIVE, false); - ZVAL_STRING(args->scan_iter, new_cursor_str); + ZVAL_STRINGL(args->scan_iter, new_cursor_str, cursor_resp->string_value_len); efree(args->cursor); efree(args); } else { From e99b275255370ae88a5986acdb44eeaaa94e31ec Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Tue, 21 Oct 2025 14:36:29 +0300 Subject: [PATCH 33/46] updares --- tests/ValkeyGlideTest.php | 1 - valkey_glide_commands.c | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/ValkeyGlideTest.php b/tests/ValkeyGlideTest.php index 11079748..c332340e 100644 --- a/tests/ValkeyGlideTest.php +++ b/tests/ValkeyGlideTest.php @@ -1755,7 +1755,6 @@ public function testlMove() public function testMove() { // Version check if needed (move has been available since early Redis versions) - $this->valkey_glide->flushAll(); //TODO remove once the select is supported again. $key1 = 'move_test_key1'; $key2 = 'move_test_key2'; $value1 = 'test_value1'; diff --git a/valkey_glide_commands.c b/valkey_glide_commands.c index fd2942ff..cc8c2528 100644 --- a/valkey_glide_commands.c +++ b/valkey_glide_commands.c @@ -492,12 +492,7 @@ int execute_object_command_impl(valkey_glide_object* valkey_glide, /* Check for batch mode */ if (valkey_glide->is_in_batch_mode) { /* Create a copy of subcommand for the callback */ - char* subcommand_copy = emalloc(subcommand_len + 1); - if (!subcommand_copy) { - return -1; - } - memcpy(subcommand_copy, subcommand, subcommand_len); - subcommand_copy[subcommand_len] = '\0'; + char* subcommand_copy = estrndup(subcommand, subcommand_len); /* Buffer command for batch execution */ int result = buffer_command_for_batch( From 816305f306d8a7abbb163ac8ed097ac26f519463 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Tue, 21 Oct 2025 15:24:01 +0300 Subject: [PATCH 34/46] updates --- valkey_glide_s_common.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/valkey_glide_s_common.c b/valkey_glide_s_common.c index 58de46be..cff2e093 100644 --- a/valkey_glide_s_common.c +++ b/valkey_glide_s_common.c @@ -642,6 +642,7 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r : COMMAND_RESPONSE_NOT_ASSOSIATIVE, false); if (args->scan_iter) { + zval_ptr_dtor(args->scan_iter); ZVAL_STRINGL(args->scan_iter, "0", 1); efree(args->cursor); efree(args); @@ -660,6 +661,7 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r /* No elements in final batch - return FALSE to terminate loop */ array_init(return_value); if (args->scan_iter) { + zval_ptr_dtor(args->scan_iter); ZVAL_STRINGL(args->scan_iter, "0", 1); efree(args->cursor); efree(args); @@ -685,6 +687,7 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r ? COMMAND_RESPONSE_SCAN_ASSOSIATIVE_ARRAY : COMMAND_RESPONSE_NOT_ASSOSIATIVE, false); + zval_ptr_dtor(args->scan_iter); ZVAL_STRINGL(args->scan_iter, new_cursor_str, cursor_resp->string_value_len); efree(args->cursor); efree(args); From 4825083ac0d2f707c78dde449f2816133e4e7f83 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Wed, 22 Oct 2025 15:38:56 +0300 Subject: [PATCH 35/46] leak fixes --- valkey_glide_s_common.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/valkey_glide_s_common.c b/valkey_glide_s_common.c index cff2e093..fcbd3aa3 100644 --- a/valkey_glide_s_common.c +++ b/valkey_glide_s_common.c @@ -528,7 +528,7 @@ int process_s_bool_result_async(CommandResponse* response, void* output, zval* r */ int process_s_set_result_async(CommandResponse* response, void* output, zval* return_value) { if (!response) { - array_init(return_value); + ZVAL_NULL(return_value); return 0; } @@ -539,8 +539,7 @@ int process_s_set_result_async(CommandResponse* response, void* output, zval* re return command_response_to_zval( response, return_value, COMMAND_RESPONSE_NOT_ASSOSIATIVE, false); } - - array_init(return_value); + ZVAL_NULL(return_value); return 0; } From 5d3d03a1028ee791ff1568319bfb3f8d38923988 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Wed, 22 Oct 2025 17:09:51 +0300 Subject: [PATCH 36/46] fix leak --- valkey_glide_hash_common.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/valkey_glide_hash_common.c b/valkey_glide_hash_common.c index a54c83a5..ea051151 100644 --- a/valkey_glide_hash_common.c +++ b/valkey_glide_hash_common.c @@ -1107,12 +1107,13 @@ int process_h_mget_result(CommandResponse* response, void* output, zval* return_ efree(args); return 0; } - /* Initialize return array */ - array_init(return_value); + /* Process the result - map back to original field names */ int ret_val = 0; if (response && response->response_type == Array) { + /* Initialize return array */ + array_init(return_value); for (int i = 0; i < args->field_count && i < response->array_value_len; i++) { zval* field = &args->fields[i]; zval field_value; @@ -1146,6 +1147,8 @@ int process_h_mget_result(CommandResponse* response, void* output, zval* return_ } } ret_val = 1; + } else { + ZVAL_NULL(return_value); } /* Free field array */ From 128ded254691df389574ea9addb555b392e02b43 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Wed, 22 Oct 2025 17:25:31 +0300 Subject: [PATCH 37/46] updates --- valkey_glide_s_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valkey_glide_s_common.c b/valkey_glide_s_common.c index fcbd3aa3..1e9f8977 100644 --- a/valkey_glide_s_common.c +++ b/valkey_glide_s_common.c @@ -592,7 +592,7 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r } else { args->cursor = "0"; } - array_init(return_value); + ZVAL_NULL(return_value); return 0; } From add31cea67b8239fae4daef57b865140df7fe557 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Wed, 22 Oct 2025 17:35:31 +0300 Subject: [PATCH 38/46] updates --- valkey_glide_s_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valkey_glide_s_common.c b/valkey_glide_s_common.c index 1e9f8977..64126fae 100644 --- a/valkey_glide_s_common.c +++ b/valkey_glide_s_common.c @@ -612,7 +612,7 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r } else { args->cursor = "0"; } - array_init(return_value); + ZVAL_NULL(return_value); return 0; } From b3de44eec093f6d5ca549311b3307503ab9fc52e Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Wed, 22 Oct 2025 17:48:21 +0300 Subject: [PATCH 39/46] updates --- valkey_glide_s_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valkey_glide_s_common.c b/valkey_glide_s_common.c index 64126fae..5da0734f 100644 --- a/valkey_glide_s_common.c +++ b/valkey_glide_s_common.c @@ -580,7 +580,7 @@ int process_s_scan_result_async(CommandResponse* response, void* output, zval* r args->cursor = "0"; } - array_init(return_value); + ZVAL_NULL(return_value); return 0; } From 4aa169f286918241b09335f2e81cc989e5acf8db Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 23 Oct 2025 11:48:03 +0300 Subject: [PATCH 40/46] more fixes --- src/client_constructor_mock.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/client_constructor_mock.c b/src/client_constructor_mock.c index f8fac3a0..2ab25b9e 100644 --- a/src/client_constructor_mock.c +++ b/src/client_constructor_mock.c @@ -59,13 +59,14 @@ static zval* build_php_connection_request(uint8_t* zval* result = NULL; if (call_user_function(NULL, NULL, &callable, &retval, 1, params) == SUCCESS) { - // Allocate return value + // Allocate return value and transfer ownership without incrementing refcount result = emalloc(sizeof(zval)); - ZVAL_COPY(result, &retval); + ZVAL_COPY_VALUE(result, &retval); + } else { + zval_ptr_dtor(&retval); } zval_ptr_dtor(&callable); - zval_ptr_dtor(&retval); zval_ptr_dtor(&buffer_param); efree(request_bytes); @@ -125,7 +126,7 @@ PHP_METHOD(ClientConstructorMock, simulate_standalone_constructor) { zval* php_request = build_php_connection_request(request_bytes, protobuf_message_len, &client_config); - RETURN_ZVAL(php_request, 1, 1); + RETURN_ZVAL(php_request, 0, 1); } /* Simulates a ValkeyGlideCluster constructor */ @@ -181,5 +182,5 @@ PHP_METHOD(ClientConstructorMock, simulate_cluster_constructor) { zval* php_request = build_php_connection_request(request_bytes, protobuf_message_len, &client_config.base); - RETURN_ZVAL(php_request, 1, 1); + RETURN_ZVAL(php_request, 0, 1); } From ddece4874a3e1baf06c5010d698cfdfdcb3dfb5a Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 23 Oct 2025 11:53:31 +0300 Subject: [PATCH 41/46] more fixes --- src/client_constructor_mock.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client_constructor_mock.c b/src/client_constructor_mock.c index 2ab25b9e..65e9cfbd 100644 --- a/src/client_constructor_mock.c +++ b/src/client_constructor_mock.c @@ -62,6 +62,8 @@ static zval* build_php_connection_request(uint8_t* // Allocate return value and transfer ownership without incrementing refcount result = emalloc(sizeof(zval)); ZVAL_COPY_VALUE(result, &retval); + // Mark retval as undefined to prevent cleanup of the transferred value + ZVAL_UNDEF(&retval); } else { zval_ptr_dtor(&retval); } From 17a4870f4e11d8fa0aff020332b50eb8cbb04486 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 23 Oct 2025 12:23:30 +0300 Subject: [PATCH 42/46] more fixes --- .vscode/settings.json | 4 +++- src/client_constructor_mock.c | 35 +++++++++++++++-------------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5fe40fd5..20c5d086 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -105,7 +105,9 @@ "valkey_glide_hash_common.h": "c", "php_var.h": "c", "valkey_glide_z_common.h": "c", - "string": "c" + "string": "c", + "__locale": "c", + "ios": "c" }, "php.validate.enable": true, "php.validate.executablePath": "/usr/bin/php", diff --git a/src/client_constructor_mock.c b/src/client_constructor_mock.c index 65e9cfbd..95596447 100644 --- a/src/client_constructor_mock.c +++ b/src/client_constructor_mock.c @@ -37,14 +37,16 @@ void register_mock_constructor_class(void) { mock_constructor_ce = register_class_ClientConstructorMock(); } -static zval* build_php_connection_request(uint8_t* request_bytes, - size_t request_len, - valkey_glide_base_client_configuration_t* base_config) { +static void build_php_connection_request(uint8_t* request_bytes, + size_t request_len, + valkey_glide_base_client_configuration_t* base_config, + zval* php_request) { if (!request_bytes) { const char* error_message = "Protobuf memory allocation error."; zend_throw_exception(get_valkey_glide_exception_ce(), error_message, 0); valkey_glide_cleanup_client_config(base_config); - return NULL; + ZVAL_NULL(php_request); + return; } zval buffer_param, callable, retval; @@ -57,23 +59,15 @@ static zval* build_php_connection_request(uint8_t* add_next_index_string(&callable, "ConnectionRequestTest"); add_next_index_string(&callable, "deserialize"); - zval* result = NULL; if (call_user_function(NULL, NULL, &callable, &retval, 1, params) == SUCCESS) { - // Allocate return value and transfer ownership without incrementing refcount - result = emalloc(sizeof(zval)); - ZVAL_COPY_VALUE(result, &retval); - // Mark retval as undefined to prevent cleanup of the transferred value - ZVAL_UNDEF(&retval); - } else { - zval_ptr_dtor(&retval); + ZVAL_COPY(php_request, &retval); } - + zval_ptr_dtor(&retval); zval_ptr_dtor(&callable); zval_ptr_dtor(&buffer_param); efree(request_bytes); valkey_glide_cleanup_client_config(base_config); - return result; } /* @@ -126,9 +120,9 @@ PHP_METHOD(ClientConstructorMock, simulate_standalone_constructor) { uint8_t* request_bytes = create_connection_request( "localhost", 6379, &protobuf_message_len, &client_config, 0, false); - zval* php_request = - build_php_connection_request(request_bytes, protobuf_message_len, &client_config); - RETURN_ZVAL(php_request, 0, 1); + zval php_request; + build_php_connection_request(request_bytes, protobuf_message_len, &client_config, &php_request); + RETURN_ZVAL(&php_request, 0, 1); } /* Simulates a ValkeyGlideCluster constructor */ @@ -182,7 +176,8 @@ PHP_METHOD(ClientConstructorMock, simulate_cluster_constructor) { client_config.periodic_checks_status, true); - zval* php_request = - build_php_connection_request(request_bytes, protobuf_message_len, &client_config.base); - RETURN_ZVAL(php_request, 0, 1); + zval php_request; + build_php_connection_request( + request_bytes, protobuf_message_len, &client_config.base, &php_request); + RETURN_ZVAL(&php_request, 0, 1); } From 76a75fe8169c249ed0a7745f36d77667d0865a91 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 23 Oct 2025 12:37:26 +0300 Subject: [PATCH 43/46] more fixes --- src/client_constructor_mock.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client_constructor_mock.c b/src/client_constructor_mock.c index 95596447..5eceece3 100644 --- a/src/client_constructor_mock.c +++ b/src/client_constructor_mock.c @@ -60,9 +60,10 @@ static void build_php_connection_request(uint8_t* add_next_index_string(&callable, "deserialize"); if (call_user_function(NULL, NULL, &callable, &retval, 1, params) == SUCCESS) { - ZVAL_COPY(php_request, &retval); + ZVAL_COPY_VALUE(php_request, &retval); + } else { + zval_ptr_dtor(&retval); } - zval_ptr_dtor(&retval); zval_ptr_dtor(&callable); zval_ptr_dtor(&buffer_param); From a532f99f021ee5a210dcaf84b792ecc3d3636247 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 23 Oct 2025 12:40:16 +0300 Subject: [PATCH 44/46] more fixes --- src/client_constructor_mock.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/client_constructor_mock.c b/src/client_constructor_mock.c index 5eceece3..882e2220 100644 --- a/src/client_constructor_mock.c +++ b/src/client_constructor_mock.c @@ -49,6 +49,13 @@ static void build_php_connection_request(uint8_t* return; } + // DEBUG: Temporarily replace call_user_function with simple object creation + // to isolate whether leak is in our code or in protobuf object creation + + // Option 1: Simple stdClass object + object_init(php_request); + + /* ORIGINAL CODE - COMMENTED OUT FOR DEBUGGING zval buffer_param, callable, retval; ZVAL_UNDEF(&retval); ZVAL_STRINGL(&buffer_param, (char*) request_bytes, request_len); @@ -66,6 +73,7 @@ static void build_php_connection_request(uint8_t* } zval_ptr_dtor(&callable); zval_ptr_dtor(&buffer_param); + */ efree(request_bytes); valkey_glide_cleanup_client_config(base_config); From edf1cd06914a13fdc0b09b108730a61c6b53f91d Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 23 Oct 2025 12:50:57 +0300 Subject: [PATCH 45/46] more fixes --- src/client_constructor_mock.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/client_constructor_mock.c b/src/client_constructor_mock.c index 882e2220..c5fac293 100644 --- a/src/client_constructor_mock.c +++ b/src/client_constructor_mock.c @@ -49,13 +49,6 @@ static void build_php_connection_request(uint8_t* return; } - // DEBUG: Temporarily replace call_user_function with simple object creation - // to isolate whether leak is in our code or in protobuf object creation - - // Option 1: Simple stdClass object - object_init(php_request); - - /* ORIGINAL CODE - COMMENTED OUT FOR DEBUGGING zval buffer_param, callable, retval; ZVAL_UNDEF(&retval); ZVAL_STRINGL(&buffer_param, (char*) request_bytes, request_len); @@ -71,9 +64,9 @@ static void build_php_connection_request(uint8_t* } else { zval_ptr_dtor(&retval); } + zval_ptr_dtor(&callable); zval_ptr_dtor(&buffer_param); - */ efree(request_bytes); valkey_glide_cleanup_client_config(base_config); @@ -129,9 +122,8 @@ PHP_METHOD(ClientConstructorMock, simulate_standalone_constructor) { uint8_t* request_bytes = create_connection_request( "localhost", 6379, &protobuf_message_len, &client_config, 0, false); - zval php_request; - build_php_connection_request(request_bytes, protobuf_message_len, &client_config, &php_request); - RETURN_ZVAL(&php_request, 0, 1); + build_php_connection_request(request_bytes, protobuf_message_len, &client_config, return_value); + // RETURN_ZVAL(&php_request, 0, 1); } /* Simulates a ValkeyGlideCluster constructor */ @@ -185,8 +177,7 @@ PHP_METHOD(ClientConstructorMock, simulate_cluster_constructor) { client_config.periodic_checks_status, true); - zval php_request; + build_php_connection_request( - request_bytes, protobuf_message_len, &client_config.base, &php_request); - RETURN_ZVAL(&php_request, 0, 1); + request_bytes, protobuf_message_len, &client_config.base, return_value); } From 0415df7ac5fa5d9942d8007076ec8b081d9675d5 Mon Sep 17 00:00:00 2001 From: asafpamzn Date: Thu, 23 Oct 2025 12:57:38 +0300 Subject: [PATCH 46/46] more fixes --- src/client_constructor_mock.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/client_constructor_mock.c b/src/client_constructor_mock.c index c5fac293..588c13c9 100644 --- a/src/client_constructor_mock.c +++ b/src/client_constructor_mock.c @@ -49,8 +49,7 @@ static void build_php_connection_request(uint8_t* return; } - zval buffer_param, callable, retval; - ZVAL_UNDEF(&retval); + zval buffer_param, callable; ZVAL_STRINGL(&buffer_param, (char*) request_bytes, request_len); zval params[1]; @@ -59,10 +58,8 @@ static void build_php_connection_request(uint8_t* add_next_index_string(&callable, "ConnectionRequestTest"); add_next_index_string(&callable, "deserialize"); - if (call_user_function(NULL, NULL, &callable, &retval, 1, params) == SUCCESS) { - ZVAL_COPY_VALUE(php_request, &retval); - } else { - zval_ptr_dtor(&retval); + if (call_user_function(NULL, NULL, &callable, php_request, 1, params) != SUCCESS) { + zval_ptr_dtor(php_request); } zval_ptr_dtor(&callable);