diff --git a/src/Caching/Storages/SQLiteStorage.php b/src/Caching/Storages/SQLiteStorage.php index 576ceb4..35f0113 100644 --- a/src/Caching/Storages/SQLiteStorage.php +++ b/src/Caching/Storages/SQLiteStorage.php @@ -21,6 +21,8 @@ class SQLiteStorage implements Nette\Caching\Storage, Nette\Caching\BulkReader { private \PDO $pdo; + /** probability that the clean() routine is started */ + public static float $gcProbability = 0.001; public function __construct(string $path) { @@ -46,6 +48,12 @@ public function __construct(string $path) CREATE INDEX IF NOT EXISTS tags_tag ON tags(tag); PRAGMA synchronous = OFF; '); + + // should we run the clean function to remove expired cached items? + if (mt_rand() / mt_getrandmax() < static::$gcProbability) { + $this->clean([]); + } + } diff --git a/tests/Storages/SQLiteStorage.clean.phpt b/tests/Storages/SQLiteStorage.clean.phpt new file mode 100644 index 0000000..d0a440d --- /dev/null +++ b/tests/Storages/SQLiteStorage.clean.phpt @@ -0,0 +1,81 @@ +save($key, 'rulez', [ + Cache::Expire => time() + 1 +]); + +// there should be one entry in the cache +Assert::same(1, countStorageEntries($storage), 'Test cache entry should be saved'); + +// wait until the item expires +sleep(2); + +// expired item should still be present in the database +Assert::same(1, countStorageEntries($storage), 'Expired item should still be in DB prior to cleanup'); + +// PHASE 2 + +// Now we will reload the storage object on the same DB file, with garbage collection probability set to 1 (100%) +SQLiteStorage::$gcProbability = 1; + +// The storage constructor should run the clean() function. +$fresh_storage = new SQLiteStorage($db_file); + +// cache DB should now be empty (zero rows) +Assert::same(0, countStorageEntries($fresh_storage), 'Expired item should be cleaned up'); + +// ok, all done + +// clean up our test file +if (file_exists($db_file) && !unlink($db_file)) { + trigger_error("Failed to clean up test database: $db_file", E_USER_WARNING); +} + + +function countStorageEntries(SQLiteStorage $storage): int { + + // because we are checking EXPIRED cache entries, we need to access the SQLite file directly, rather than use the standard SqliteStorage methods + try { + + // we use Reflection to access the private 'pdo' property of the SQLiteStorage object + $reflection = new ReflectionProperty(SQLiteStorage::class, 'pdo'); + $reflection->setAccessible(true); + $pdo = $reflection->getValue($storage); + + // Now we use the storage object's own PDO connection to count the number of rows in the cache + $stmt = $pdo->prepare('SELECT COUNT(*) FROM cache'); + $stmt->execute(); + return (int) $stmt->fetchColumn(); + } catch (ReflectionException $e) { + Assert::fail('Unable to access PDO property: ' . $e->getMessage()); + } + +} \ No newline at end of file