diff --git a/src/Database.php b/src/Database.php index 223302fb..a7d86bc2 100644 --- a/src/Database.php +++ b/src/Database.php @@ -11,6 +11,7 @@ namespace Cycle\Database; +use Cycle\Database\Driver\Driver; use Cycle\Database\Driver\DriverInterface; use Cycle\Database\Query\DeleteQuery; use Cycle\Database\Query\InsertQuery; @@ -208,4 +209,26 @@ public function rollback(): bool { return $this->getDriver(self::WRITE)->rollbackTransaction(); } + + public function withoutCache(): self + { + $database = clone $this; + + if ($this->readDriver instanceof Driver && $database->readDriver !== $database->driver) { + $database->readDriver = $database->readDriver->withoutCache(); + } + + if ($this->driver instanceof Driver) { + if ($database->readDriver === $database->driver) { + $database->readDriver = $database->readDriver->withoutCache(); + $database->driver = $database->readDriver; + } else { + $database->driver = $database->driver->withoutCache(); + } + + return $database; + } + + return $this; + } } diff --git a/src/DatabaseInterface.php b/src/DatabaseInterface.php index 0e862c51..25bf6b43 100644 --- a/src/DatabaseInterface.php +++ b/src/DatabaseInterface.php @@ -149,4 +149,9 @@ public function commit(): bool; * Rollback the active database transaction. */ public function rollback(): bool; + + /** + * Will be added in next major release. + */ + // public function withoutCache(): self; } diff --git a/src/Driver/Driver.php b/src/Driver/Driver.php index 57619692..3f8cbb9b 100644 --- a/src/Driver/Driver.php +++ b/src/Driver/Driver.php @@ -54,6 +54,7 @@ abstract class Driver implements DriverInterface, NamedInterface, LoggerAwareInt /** @var PDOStatement[]|PDOStatementInterface[] */ protected array $queryCache = []; private ?string $name = null; + private bool $useCache = true; protected function __construct( protected DriverConfig $config, @@ -61,10 +62,12 @@ protected function __construct( protected CompilerInterface $queryCompiler, BuilderInterface $queryBuilder ) { + $this->useCache = $this->config->queryCache; + $this->schemaHandler = $schemaHandler->withDriver($this); $this->queryBuilder = $queryBuilder->withDriver($this); - if ($this->config->queryCache && $queryCompiler instanceof CachingCompilerInterface) { + if ($this->useCache && $queryCompiler instanceof CachingCompilerInterface) { $this->queryCompiler = new CompilerCache($queryCompiler); } @@ -96,6 +99,14 @@ public function isReadonly(): bool return $this->config->readonly; } + public function withoutCache(): static + { + $driver = clone $this; + $driver->useCache = false; + + return $driver; + } + /** * Disconnect and destruct. */ @@ -480,12 +491,12 @@ protected function statement(string $query, iterable $parameters = [], bool $ret */ protected function prepare(string $query): PDOStatement|PDOStatementInterface { - if ($this->config->queryCache && isset($this->queryCache[$query])) { + if ($this->useCache && isset($this->queryCache[$query])) { return $this->queryCache[$query]; } $statement = $this->getPDO()->prepare($query); - if ($this->config->queryCache) { + if ($this->useCache) { $this->queryCache[$query] = $statement; } diff --git a/tests/Database/Unit/DatabaseTest.php b/tests/Database/Unit/DatabaseTest.php new file mode 100644 index 00000000..224e7735 --- /dev/null +++ b/tests/Database/Unit/DatabaseTest.php @@ -0,0 +1,135 @@ +createMock(Driver::class); + + $driver + ->expects($this->once()) + ->method('withoutCache'); + + $database = new Database('default', '', $driver); + + $newDb = $database->withoutCache(); + + $ref = new \ReflectionProperty($newDb, 'readDriver'); + $ref->setAccessible(true); + + $this->assertNull($ref->getValue($newDb)); + $this->assertNotSame($driver, $newDb->getDriver()); + } + + public function testWithoutCacheWithSameDriverAndReadDriver(): void + { + $driver = $this->createMock(Driver::class); + + $driver + ->expects($this->once()) + ->method('withoutCache'); + + $database = new Database('default', '', $driver, $driver); + + $newDb = $database->withoutCache(); + + $refDriver = new \ReflectionProperty($newDb, 'driver'); + $refDriver->setAccessible(true); + + $refReadDriver = new \ReflectionProperty($newDb, 'readDriver'); + $refReadDriver->setAccessible(true); + + $this->assertSame($refDriver->getValue($newDb), $refReadDriver->getValue($newDb)); + } + + public function testWithoutCacheWithDriverAndReadDriver(): void + { + $driver = $this->createMock(Driver::class); + $readDriver = $this->createMock(Driver::class); + + $driver + ->expects($this->once()) + ->method('withoutCache'); + + $readDriver + ->expects($this->once()) + ->method('withoutCache'); + + $database = new Database('default', '', $driver, $readDriver); + + $newDb = $database->withoutCache(); + + $refDriver = new \ReflectionProperty($newDb, 'driver'); + $refDriver->setAccessible(true); + + $refReadDriver = new \ReflectionProperty($newDb, 'readDriver'); + $refReadDriver->setAccessible(true); + + $this->assertNotSame($refDriver->getValue($newDb), $refReadDriver->getValue($newDb)); + $this->assertNotSame($driver, $refDriver->getValue($newDb)); + $this->assertNotSame($readDriver, $refReadDriver->getValue($newDb)); + } + + public function testWithoutCacheWithoutReadDriverAndWithoutMethod(): void + { + $driver = $this->createMock(DriverInterface::class); + + $database = new Database('default', '', $driver); + + $newDb = $database->withoutCache(); + + $ref = new \ReflectionProperty($newDb, 'readDriver'); + $ref->setAccessible(true); + + $this->assertNull($ref->getValue($newDb)); + $this->assertSame($driver, $newDb->getDriver()); + } + + public function testWithoutCacheWithDriverAndReadDriverWithoutMethod(): void + { + $driver = $this->createMock(DriverInterface::class); + $readDriver = $this->createMock(DriverInterface::class); + + $database = new Database('default', '', $driver, $readDriver); + + $newDb = $database->withoutCache(); + + $refDriver = new \ReflectionProperty($newDb, 'driver'); + $refDriver->setAccessible(true); + + $refReadDriver = new \ReflectionProperty($newDb, 'readDriver'); + $refReadDriver->setAccessible(true); + + $this->assertNotSame($refDriver->getValue($newDb), $refReadDriver->getValue($newDb)); + $this->assertSame($driver, $refDriver->getValue($newDb)); + $this->assertSame($readDriver, $refReadDriver->getValue($newDb)); + } + + public function testWithoutCacheWithSameDriversAndWithoutMethod(): void + { + $driver = $this->createMock(DriverInterface::class); + + $database = new Database('default', '', $driver, $driver); + + $newDb = $database->withoutCache(); + + $refDriver = new \ReflectionProperty($newDb, 'driver'); + $refDriver->setAccessible(true); + + $refReadDriver = new \ReflectionProperty($newDb, 'readDriver'); + $refReadDriver->setAccessible(true); + + $this->assertSame($refDriver->getValue($newDb), $refReadDriver->getValue($newDb)); + $this->assertSame($driver, $refDriver->getValue($newDb)); + $this->assertSame($driver, $refReadDriver->getValue($newDb)); + } +} diff --git a/tests/Database/Unit/Driver/AbstractDriverTest.php b/tests/Database/Unit/Driver/AbstractDriverTest.php index ff380478..d966c152 100644 --- a/tests/Database/Unit/Driver/AbstractDriverTest.php +++ b/tests/Database/Unit/Driver/AbstractDriverTest.php @@ -147,6 +147,40 @@ public function testLogsWithEnabledInterpolation(): void ); } + public function testUseCacheFromConfig(): void + { + $ref = new \ReflectionProperty(Driver::class, 'useCache'); + $ref->setAccessible(true); + + $this->assertTrue($ref->getValue(TestDriver::create(new SQLiteDriverConfig(queryCache: true)))); + $this->assertFalse($ref->getValue(TestDriver::create(new SQLiteDriverConfig(queryCache: false)))); + } + + public function testWithoutCache(): void + { + $ref = new \ReflectionProperty(Driver::class, 'useCache'); + $ref->setAccessible(true); + + $driver = TestDriver::create(new SQLiteDriverConfig(queryCache: true)); + + $this->assertTrue($ref->getValue($driver)); + $this->assertFalse($ref->getValue($driver->withoutCache())); + } + + public function testPdoNotClonedAfterCacheDisabled(): void + { + $ref = new \ReflectionMethod(Driver::class, 'getPDO'); + $ref->setAccessible(true); + + $driver = TestDriver::create(new SQLiteDriverConfig(queryCache: true)); + $oldPDO = $ref->invoke($driver); + + $driver = $driver->withoutCache(); + $newPDO = $ref->invoke($driver); + + $this->assertSame($oldPDO, $newPDO); + } + private function checkImmutability(DriverInterface $driver, DriverInterface $newDriver): void { // Immutability