Skip to content

fix(cache): add check for redis empty results in deleteMatching #4628

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions system/Cache/Handlers/DummyHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ public function delete(string $key)
*
* @param string $pattern Cache items glob-style pattern
*
* @return boolean
* @return integer The number of deleted items
*/
public function deleteMatching(string $pattern)
{
return true;
return 0;
}

//--------------------------------------------------------------------
Expand Down
12 changes: 6 additions & 6 deletions system/Cache/Handlers/FileHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,21 +165,21 @@ public function delete(string $key)
*
* @param string $pattern Cache items glob-style pattern
*
* @return boolean
* @return integer The number of deleted items
*/
public function deleteMatching(string $pattern)
{
$success = true;
$deleted = 0;

foreach (glob($this->path . $pattern) as $filename)
foreach (glob($this->path . $pattern, GLOB_NOSORT) as $filename)
{
if (! is_file($filename) || ! @unlink($filename))
if (is_file($filename) && @unlink($filename))
{
$success = false;
$deleted++;
}
}

return $success;
return $deleted;
}

//--------------------------------------------------------------------
Expand Down
12 changes: 5 additions & 7 deletions system/Cache/Handlers/PredisHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,21 +190,19 @@ public function delete(string $key)
*
* @param string $pattern Cache items glob-style pattern
*
* @return boolean
* @return integer The number of deleted items
*/
public function deleteMatching(string $pattern)
{
$success = true;
$deleted = 0;
$matchedKeys = [];

foreach (new Keyspace($this->redis, $pattern) as $key)
{
if ($this->redis->del($key) !== 1)
{
$success = false;
}
$matchedKeys[] = $key;
}

return $success;
return $this->redis->del($matchedKeys);
}

//--------------------------------------------------------------------
Expand Down
19 changes: 12 additions & 7 deletions system/Cache/Handlers/RedisHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,25 +228,30 @@ public function delete(string $key)
*
* @param string $pattern Cache items glob-style pattern
*
* @return boolean
* @return integer The number of deleted items
*/
public function deleteMatching(string $pattern)
{
$success = true;
$matchedKeys = [];
$iterator = null;

while ($keys = $this->redis->scan($iterator, $pattern))
do
{
foreach ($keys as $key)
// Scan for some keys
$keys = $this->redis->scan($iterator, $pattern);

// Redis may return empty results, so protect against that
if ($keys !== false)
{
if ($this->redis->del($key) !== 1)
foreach ($keys as $key)
{
$success = false;
$matchedKeys[] = $key;
}
}
}
while ($iterator > 0); // @phpstan-ignore-line

return $success;
return $this->redis->del($matchedKeys);
}

//--------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion tests/system/Cache/Handlers/DummyHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public function testDelete()

public function testDeleteMatching()
{
$this->assertTrue($this->dummyHandler->deleteMatching('key*'));
$this->assertSame(0, $this->dummyHandler->deleteMatching('key*'));
}

public function testIncrement()
Expand Down
54 changes: 38 additions & 16 deletions tests/system/Cache/Handlers/FileHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@ class FileHandlerTest extends CIUnitTestCase
private static $key1 = 'key1';
private static $key2 = 'key2';
private static $key3 = 'key3';
private static $key4 = 'another_key';

private static function getKeyArray()
{
return [
self::$key1,
self::$key2,
self::$key3,
self::$key4,
];
}

Expand Down Expand Up @@ -135,24 +133,48 @@ public function testDelete()
$this->assertFalse($this->fileHandler->delete(self::$dummy));
}

public function testDeleteMatching()
public function testDeleteMatchingPrefix()
{
$this->fileHandler->save(self::$key1, 'value');
$this->fileHandler->save(self::$key2, 'value2');
$this->fileHandler->save(self::$key3, 'value3');
$this->fileHandler->save(self::$key4, 'value4');
// Save 101 items to match on
for ($i = 1; $i <= 101; $i++)
{
$this->fileHandler->save('key_' . $i, 'value' . $i);
}

$this->assertSame('value', $this->fileHandler->get(self::$key1));
$this->assertSame('value2', $this->fileHandler->get(self::$key2));
$this->assertSame('value3', $this->fileHandler->get(self::$key3));
$this->assertSame('value4', $this->fileHandler->get(self::$key4));
// check that there are 101 items is cache store
$this->assertSame(101, count($this->fileHandler->getCacheInfo()));

$this->assertTrue($this->fileHandler->deleteMatching('key*'));
// Checking that given the prefix "key_1", deleteMatching deletes 13 keys:
// (key_1, key_10, key_11, key_12, key_13, key_14, key_15, key_16, key_17, key_18, key_19, key_100, key_101)
$this->assertSame(13, $this->fileHandler->deleteMatching('key_1*'));

$this->assertNull($this->fileHandler->get(self::$key1));
$this->assertNull($this->fileHandler->get(self::$key2));
$this->assertNull($this->fileHandler->get(self::$key3));
$this->assertSame('value4', $this->fileHandler->get(self::$key4));
// check that there remains (101 - 13) = 88 items is cache store
$this->assertSame(88, count($this->fileHandler->getCacheInfo()));

// Clear all files
$this->fileHandler->clean();
}

public function testDeleteMatchingSuffix()
{
// Save 101 items to match on
for ($i = 1; $i <= 101; $i++)
{
$this->fileHandler->save('key_' . $i, 'value' . $i);
}

// check that there are 101 items is cache store
$this->assertSame(101, count($this->fileHandler->getCacheInfo()));

// Checking that given the suffix "1", deleteMatching deletes 11 keys:
// (key_1, key_11, key_21, key_31, key_41, key_51, key_61, key_71, key_81, key_91, key_101)
$this->assertSame(11, $this->fileHandler->deleteMatching('*1'));

// check that there remains (101 - 13) = 88 items is cache store
$this->assertSame(90, count($this->fileHandler->getCacheInfo()));

// Clear all files
$this->fileHandler->clean();
}

public function testIncrement()
Expand Down
48 changes: 32 additions & 16 deletions tests/system/Cache/Handlers/PredisHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ class PredisHandlerTest extends CIUnitTestCase
private static $key1 = 'key1';
private static $key2 = 'key2';
private static $key3 = 'key3';
private static $key4 = 'another_key';

private static function getKeyArray()
{
return [
self::$key1,
self::$key2,
self::$key3,
self::$key4,
];
}

Expand Down Expand Up @@ -100,24 +98,42 @@ public function testDelete()
$this->assertFalse($this->PredisHandler->delete(self::$dummy));
}

public function testDeleteMatching()
public function testDeleteMatchingPrefix()
{
$this->PredisHandler->save(self::$key1, 'value');
$this->PredisHandler->save(self::$key2, 'value2');
$this->PredisHandler->save(self::$key3, 'value3');
$this->PredisHandler->save(self::$key4, 'value4');
// Save 101 items to match on
for ($i = 1; $i <= 101; $i++)
{
$this->PredisHandler->save('key_' . $i, 'value' . $i);
}

$this->assertSame('value', $this->PredisHandler->get(self::$key1));
$this->assertSame('value2', $this->PredisHandler->get(self::$key2));
$this->assertSame('value3', $this->PredisHandler->get(self::$key3));
$this->assertSame('value4', $this->PredisHandler->get(self::$key4));
// check that there are 101 items is cache store
$this->assertSame('101', $this->PredisHandler->getCacheInfo()['Keyspace']['db0']['keys']);

$this->assertTrue($this->PredisHandler->deleteMatching('key*'));
// Checking that given the prefix "key_1", deleteMatching deletes 13 keys:
// (key_1, key_10, key_11, key_12, key_13, key_14, key_15, key_16, key_17, key_18, key_19, key_100, key_101)
$this->assertSame(13, $this->PredisHandler->deleteMatching('key_1*'));

$this->assertNull($this->PredisHandler->get(self::$key1));
$this->assertNull($this->PredisHandler->get(self::$key2));
$this->assertNull($this->PredisHandler->get(self::$key3));
$this->assertSame('value4', $this->PredisHandler->get(self::$key4));
// check that there remains (101 - 13) = 88 items is cache store
$this->assertSame('88', $this->PredisHandler->getCacheInfo()['Keyspace']['db0']['keys']);
}

public function testDeleteMatchingSuffix()
{
// Save 101 items to match on
for ($i = 1; $i <= 101; $i++)
{
$this->PredisHandler->save('key_' . $i, 'value' . $i);
}

// check that there are 101 items is cache store
$this->assertSame('101', $this->PredisHandler->getCacheInfo()['Keyspace']['db0']['keys']);

// Checking that given the suffix "1", deleteMatching deletes 11 keys:
// (key_1, key_11, key_21, key_31, key_41, key_51, key_61, key_71, key_81, key_91, key_101)
$this->assertSame(11, $this->PredisHandler->deleteMatching('*1'));

// check that there remains (101 - 13) = 88 items is cache store
$this->assertSame('90', $this->PredisHandler->getCacheInfo()['Keyspace']['db0']['keys']);
}

public function testClean()
Expand Down
53 changes: 36 additions & 17 deletions tests/system/Cache/Handlers/RedisHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ class RedisHandlerTest extends CIUnitTestCase
private static $key1 = 'key1';
private static $key2 = 'key2';
private static $key3 = 'key3';
private static $key4 = 'another_key';

private static function getKeyArray()
{
return [
self::$key1,
self::$key2,
self::$key3,
self::$key4,
];
}

Expand Down Expand Up @@ -100,24 +98,46 @@ public function testDelete()
$this->assertFalse($this->redisHandler->delete(self::$dummy));
}

public function testDeleteMatching()
public function testDeleteMatchingPrefix()
{
$this->redisHandler->save(self::$key1, 'value');
$this->redisHandler->save(self::$key2, 'value2');
$this->redisHandler->save(self::$key3, 'value3');
$this->redisHandler->save(self::$key4, 'value4');
// Save 101 items to match on
for ($i = 1; $i <= 101; $i++)
{
$this->redisHandler->save('key_' . $i, 'value' . $i);
}

$this->assertSame('value', $this->redisHandler->get(self::$key1));
$this->assertSame('value2', $this->redisHandler->get(self::$key2));
$this->assertSame('value3', $this->redisHandler->get(self::$key3));
$this->assertSame('value4', $this->redisHandler->get(self::$key4));
// check that there are 101 items is cache store
$dbInfo = explode(',', $this->redisHandler->getCacheInfo()['db0']);
$this->assertSame('keys=101', $dbInfo[0]);

$this->assertTrue($this->redisHandler->deleteMatching('key*'));
// Checking that given the prefix "key_1", deleteMatching deletes 13 keys:
// (key_1, key_10, key_11, key_12, key_13, key_14, key_15, key_16, key_17, key_18, key_19, key_100, key_101)
$this->assertSame(13, $this->redisHandler->deleteMatching('key_1*'));

$this->assertNull($this->redisHandler->get(self::$key1));
$this->assertNull($this->redisHandler->get(self::$key2));
$this->assertNull($this->redisHandler->get(self::$key3));
$this->assertSame('value4', $this->redisHandler->get(self::$key4));
// check that there remains (101 - 13) = 88 items is cache store
$dbInfo = explode(',', $this->redisHandler->getCacheInfo()['db0']);
$this->assertSame('keys=88', $dbInfo[0]);
}

public function testDeleteMatchingSuffix()
{
// Save 101 items to match on
for ($i = 1; $i <= 101; $i++)
{
$this->redisHandler->save('key_' . $i, 'value' . $i);
}

// check that there are 101 items is cache store
$dbInfo = explode(',', $this->redisHandler->getCacheInfo()['db0']);
$this->assertSame('keys=101', $dbInfo[0]);

// Checking that given the suffix "1", deleteMatching deletes 11 keys:
// (key_1, key_11, key_21, key_31, key_41, key_51, key_61, key_71, key_81, key_91, key_101)
$this->assertSame(11, $this->redisHandler->deleteMatching('*1'));

// check that there remains (101 - 13) = 88 items is cache store
$dbInfo = explode(',', $this->redisHandler->getCacheInfo()['db0']);
$this->assertSame('keys=90', $dbInfo[0]);
}

//FIXME: I don't like all Hash logic very much. It's wasting memory.
Expand All @@ -132,7 +152,6 @@ public function testDeleteMatching()
public function testClean()
{
$this->redisHandler->save(self::$key1, 1);
$this->redisHandler->save(self::$key2, 'value');

$this->assertTrue($this->redisHandler->clean());
}
Expand Down
13 changes: 6 additions & 7 deletions user_guide_src/source/libraries/caching.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,23 +136,22 @@ Class Reference

$cache->delete('cache_item_id');

.. php:method:: deleteMatching($pattern): bool
.. php:method:: deleteMatching($pattern): integer

:param string $pattern: glob-style pattern to match cached items keys
:returns: ``true`` on success, ``false`` if one of the deletes fails
:rtype: bool
:returns: number of deleted items
:rtype: integer

This method will delete multiple items from the cache store at once by
matching their keys against a glob pattern.
If one of the cache item deletion fails, the method will return FALSE.
matching their keys against a glob-style pattern. It will return the total number of deleted items.

.. important:: This method is only implemented for File, Redis and Predis handlers.
Due to limitations, it couldn't be implemented for Memcached and Wincache handlers.

Example::

$cache->deleteMatching('prefix_*'); // deletes all keys starting with "prefix_"
$cache->deleteMatching('*_suffix'); // deletes all keys ending with "_suffix"
$cache->deleteMatching('prefix_*'); // deletes all items of which keys start with "prefix_"
$cache->deleteMatching('*_suffix'); // deletes all items of which keys end with "_suffix"

For more information on glob-style syntax, please see
`https://en.wikipedia.org/wiki/Glob_(programming) <https://en.wikipedia.org/wiki/Glob_(programming)#Syntax>`_.
Expand Down