Skip to content

Commit ccdf227

Browse files
committed
[Cache][Lock] Fix PDO store not creating table + add tests
1 parent a454d0c commit ccdf227

File tree

6 files changed

+102
-31
lines changed

6 files changed

+102
-31
lines changed

src/Symfony/Component/Cache/Adapter/PdoAdapter.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ protected function doSave(array $values, int $lifetime)
507507
try {
508508
$stmt = $conn->prepare($sql);
509509
} catch (\PDOException $e) {
510-
if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
510+
if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true))) {
511511
$this->createTable();
512512
}
513513
$stmt = $conn->prepare($sql);
@@ -542,7 +542,7 @@ protected function doSave(array $values, int $lifetime)
542542
try {
543543
$stmt->execute();
544544
} catch (\PDOException $e) {
545-
if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
545+
if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true))) {
546546
$this->createTable();
547547
}
548548
$stmt->execute();
@@ -596,4 +596,21 @@ private function getServerVersion(): string
596596

597597
return $this->serverVersion;
598598
}
599+
600+
private function isTableMissing(\PDOException $exception): bool
601+
{
602+
$driver = $this->driver;
603+
$code = $exception->getCode();
604+
605+
switch (true) {
606+
case 'pgsql' === $driver && '42P01' === $code:
607+
case 'sqlite' === $driver && str_contains($exception->getMessage(), 'no such table:'):
608+
case 'oci' === $driver && 942 === $code:
609+
case 'sqlsrv' === $driver && 208 === $code:
610+
case 'mysql' === $driver && 1146 === $code:
611+
return true;
612+
default:
613+
return false;
614+
}
615+
}
599616
}

src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@
1818
use Doctrine\DBAL\DriverManager;
1919
use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory;
2020
use Doctrine\DBAL\Schema\Schema;
21-
use PHPUnit\Framework\SkippedTestSuiteError;
2221
use Psr\Cache\CacheItemPoolInterface;
2322
use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter;
2423
use Symfony\Component\Cache\Tests\Fixtures\DriverWrapper;
2524

2625
/**
26+
* @requires extension pdo_sqlite
27+
*
2728
* @group time-sensitive
2829
*/
2930
class DoctrineDbalAdapterTest extends AdapterTestCase
@@ -32,10 +33,6 @@ class DoctrineDbalAdapterTest extends AdapterTestCase
3233

3334
public static function setUpBeforeClass(): void
3435
{
35-
if (!\extension_loaded('pdo_sqlite')) {
36-
throw new SkippedTestSuiteError('Extension pdo_sqlite required.');
37-
}
38-
3936
self::$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache');
4037
}
4138

@@ -107,13 +104,15 @@ public function testConfigureSchemaTableExists()
107104
}
108105

109106
/**
107+
* @group legacy
108+
* @group integration
109+
*
110110
* @dataProvider provideDsn
111111
*/
112-
public function testDsn(string $dsn, string $file = null)
112+
public function testDsn(string $dsn, string $pdoDsn, string $file = null)
113113
{
114114
try {
115115
$pool = new DoctrineDbalAdapter($dsn);
116-
$pool->createTable();
117116

118117
$item = $pool->getItem('key');
119118
$item->set('value');
@@ -122,15 +121,22 @@ public function testDsn(string $dsn, string $file = null)
122121
if (null !== $file) {
123122
@unlink($file);
124123
}
124+
125+
$pdo = new \PDO($pdoDsn);
126+
$pdo->exec('DROP TABLE IF EXISTS cache_items');
125127
}
126128
}
127129

128130
public static function provideDsn()
129131
{
130132
$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache');
131-
yield ['sqlite://localhost/'.$dbFile.'1', $dbFile.'1'];
132-
yield ['sqlite3:///'.$dbFile.'3', $dbFile.'3'];
133-
yield ['sqlite://localhost/:memory:'];
133+
yield 'SQLite file' => ['sqlite://localhost/'.$dbFile.'1', 'sqlite:'.$dbFile.'1', $dbFile.'1'];
134+
yield 'SQLite3 file' => ['sqlite3:///'.$dbFile.'3', 'sqlite:'.$dbFile.'3', $dbFile.'3'];
135+
yield 'SQLite in memory' => ['sqlite://localhost/:memory:', 'sqlite::memory:'];
136+
137+
if ($host = getenv('POSTGRES_HOST')) {
138+
yield 'PostgreSQL' => ['pgsql://postgres:password@'.$host, 'pgsql:host='.$host.';user=postgres;password=password'];
139+
}
134140
}
135141

136142
protected function isPruned(DoctrineDbalAdapter $cache, string $name): bool

src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@
1111

1212
namespace Symfony\Component\Cache\Tests\Adapter;
1313

14-
use PHPUnit\Framework\SkippedTestSuiteError;
1514
use Psr\Cache\CacheItemPoolInterface;
1615
use Symfony\Component\Cache\Adapter\PdoAdapter;
1716

1817
/**
18+
* @requires extension pdo_sqlite
19+
*
1920
* @group time-sensitive
2021
*/
2122
class PdoAdapterTest extends AdapterTestCase
@@ -24,10 +25,6 @@ class PdoAdapterTest extends AdapterTestCase
2425

2526
public static function setUpBeforeClass(): void
2627
{
27-
if (!\extension_loaded('pdo_sqlite')) {
28-
throw new SkippedTestSuiteError('Extension pdo_sqlite required.');
29-
}
30-
3128
self::$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache');
3229

3330
$pool = new PdoAdapter('sqlite:'.self::$dbFile);
@@ -71,13 +68,14 @@ public function testCleanupExpiredItems()
7168
}
7269

7370
/**
71+
* @group integration
72+
*
7473
* @dataProvider provideDsn
7574
*/
7675
public function testDsn(string $dsn, string $file = null)
7776
{
7877
try {
7978
$pool = new PdoAdapter($dsn);
80-
$pool->createTable();
8179

8280
$item = $pool->getItem('key');
8381
$item->set('value');
@@ -86,14 +84,21 @@ public function testDsn(string $dsn, string $file = null)
8684
if (null !== $file) {
8785
@unlink($file);
8886
}
87+
88+
$pdo = new \PDO($dsn);
89+
$pdo->exec('DROP TABLE IF EXISTS cache_items');
8990
}
9091
}
9192

9293
public static function provideDsn()
9394
{
9495
$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache');
95-
yield ['sqlite:'.$dbFile.'2', $dbFile.'2'];
96-
yield ['sqlite::memory:'];
96+
yield 'SQLite file' => ['sqlite:'.$dbFile.'2', $dbFile.'2'];
97+
yield 'SQLite in memory' => ['sqlite::memory:'];
98+
99+
if ($host = getenv('POSTGRES_HOST')) {
100+
yield 'PostgreSQL' => ['pgsql:host='.$host.';user=postgres;password=password'];
101+
}
97102
}
98103

99104
protected function isPruned(PdoAdapter $cache, string $name): bool

src/Symfony/Component/Lock/Store/PdoStore.php

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public function save(Key $key)
115115
try {
116116
$stmt = $conn->prepare($sql);
117117
} catch (\PDOException $e) {
118-
if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
118+
if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true))) {
119119
$this->createTable();
120120
}
121121
$stmt = $conn->prepare($sql);
@@ -127,8 +127,18 @@ public function save(Key $key)
127127
try {
128128
$stmt->execute();
129129
} catch (\PDOException $e) {
130-
// the lock is already acquired. It could be us. Let's try to put off.
131-
$this->putOffExpiration($key, $this->initialTtl);
130+
if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true))) {
131+
$this->createTable();
132+
133+
try {
134+
$stmt->execute();
135+
} catch (\PDOException $e) {
136+
$this->putOffExpiration($key, $this->initialTtl);
137+
}
138+
} else {
139+
// the lock is already acquired. It could be us. Let's try to put off.
140+
$this->putOffExpiration($key, $this->initialTtl);
141+
}
132142
}
133143

134144
$this->randomlyPrune();
@@ -316,4 +326,21 @@ private function getCurrentTimestampStatement(): string
316326
return (string) time();
317327
}
318328
}
329+
330+
private function isTableMissing(\PDOException $exception): bool
331+
{
332+
$driver = $this->getDriver();
333+
$code = $exception->getCode();
334+
335+
switch (true) {
336+
case 'pgsql' === $driver && '42P01' === $code:
337+
case 'sqlite' === $driver && str_contains($exception->getMessage(), 'no such table:'):
338+
case 'oci' === $driver && 942 === $code:
339+
case 'sqlsrv' === $driver && 208 === $code:
340+
case 'mysql' === $driver && 1146 === $code:
341+
return true;
342+
default:
343+
return false;
344+
}
345+
}
319346
}

src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,11 @@ public function testAbortAfterExpiration()
7979
}
8080

8181
/**
82+
* @group integration
83+
*
8284
* @dataProvider provideDsn
8385
*/
84-
public function testDsn(string $dsn, string $file = null)
86+
public function testDsn(string $dsn, string $pdoDsn, string $file = null)
8587
{
8688
$key = new Key(uniqid(__METHOD__, true));
8789

@@ -94,15 +96,22 @@ public function testDsn(string $dsn, string $file = null)
9496
if (null !== $file) {
9597
@unlink($file);
9698
}
99+
100+
$pdo = new \PDO($pdoDsn);
101+
$pdo->exec('DROP TABLE IF EXISTS lock_keys');
97102
}
98103
}
99104

100105
public static function provideDsn()
101106
{
102107
$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache');
103-
yield ['sqlite://localhost/'.$dbFile.'1', $dbFile.'1'];
104-
yield ['sqlite3:///'.$dbFile.'3', $dbFile.'3'];
105-
yield ['sqlite://localhost/:memory:'];
108+
yield 'SQLite file' => ['sqlite://localhost/'.$dbFile.'1', 'sqlite:'.$dbFile.'1', $dbFile.'1'];
109+
yield 'SQLite3 file' => ['sqlite3:///'.$dbFile.'3', 'sqlite:'.$dbFile.'3', $dbFile.'3'];
110+
yield 'SQLite in memory' => ['sqlite://localhost/:memory:', 'sqlite::memory:'];
111+
112+
if ($host = getenv('POSTGRES_HOST')) {
113+
yield 'PostgreSQL' => ['pgsql://postgres:password@'.$host, 'pgsql:host='.$host.';user=postgres;password=password'];
114+
}
106115
}
107116

108117
/**

src/Symfony/Component/Lock/Tests/Store/PdoStoreTest.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
* @author Jérémy Derussé <jeremy@derusse.com>
2121
*
2222
* @requires extension pdo_sqlite
23-
*
24-
* @group integration
2523
*/
2624
class PdoStoreTest extends AbstractStoreTestCase
2725
{
@@ -78,6 +76,8 @@ public function testInvalidTtlConstruct()
7876
}
7977

8078
/**
79+
* @group integration
80+
*
8181
* @dataProvider provideDsn
8282
*/
8383
public function testDsn(string $dsn, string $file = null)
@@ -93,13 +93,20 @@ public function testDsn(string $dsn, string $file = null)
9393
if (null !== $file) {
9494
@unlink($file);
9595
}
96+
97+
$pdo = new \PDO($dsn);
98+
$pdo->exec('DROP TABLE IF EXISTS lock_keys');
9699
}
97100
}
98101

99102
public static function provideDsn()
100103
{
101104
$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache');
102-
yield ['sqlite:'.$dbFile.'2', $dbFile.'2'];
103-
yield ['sqlite::memory:'];
105+
yield 'SQLite file' => ['sqlite:'.$dbFile.'2', $dbFile.'2'];
106+
yield 'SQLite in memory' => ['sqlite::memory:'];
107+
108+
if ($host = getenv('POSTGRES_HOST')) {
109+
yield 'PostgreSQL' => ['pgsql:host='.$host.';user=postgres;password=password'];
110+
}
104111
}
105112
}

0 commit comments

Comments
 (0)