Skip to content

Commit 0431dc3

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

File tree

6 files changed

+101
-31
lines changed

6 files changed

+101
-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: 15 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,17 @@ public function testConfigureSchemaTableExists()
107104
}
108105

109106
/**
107+
* @group integration
108+
*
110109
* @dataProvider provideDsn
111110
*/
112-
public function testDsn(string $dsn, string $file = null)
111+
public function testDsn(string $dsn, string $pdoDsn, string $file = null)
113112
{
113+
$pdo = new \PDO($pdoDsn);
114+
$pdo->exec('DROP TABLE IF EXISTS cache_items');
115+
114116
try {
115117
$pool = new DoctrineDbalAdapter($dsn);
116-
$pool->createTable();
117118

118119
$item = $pool->getItem('key');
119120
$item->set('value');
@@ -128,9 +129,13 @@ public function testDsn(string $dsn, string $file = null)
128129
public static function provideDsn()
129130
{
130131
$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:'];
132+
yield 'SQLite file' => ['sqlite://localhost/'.$dbFile.'1', 'sqlite:'.$dbFile.'1', $dbFile.'1'];
133+
yield 'SQLite3 file' => ['sqlite3:///'.$dbFile.'3', 'sqlite:'.$dbFile.'3', $dbFile.'3'];
134+
yield 'SQLite in memory' => ['sqlite://localhost/:memory:', 'sqlite::memory:'];
135+
136+
if ($host = getenv('POSTGRES_HOST')) {
137+
yield 'PostgreSQL' => ['pgsql://postgres:password@'.$host, 'pgsql:host='.$host.';user=postgres;password=password'];
138+
}
134139
}
135140

136141
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,17 @@ public function testCleanupExpiredItems()
7168
}
7269

7370
/**
71+
* @group integration
72+
*
7473
* @dataProvider provideDsn
7574
*/
7675
public function testDsn(string $dsn, string $file = null)
7776
{
77+
$pdo = new \PDO($dsn);
78+
$pdo->exec('DROP TABLE IF EXISTS cache_items');
79+
7880
try {
7981
$pool = new PdoAdapter($dsn);
80-
$pool->createTable();
8182

8283
$item = $pool->getItem('key');
8384
$item->set('value');
@@ -92,8 +93,12 @@ public function testDsn(string $dsn, string $file = null)
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,10 +79,15 @@ 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
{
88+
$pdo = new \PDO($pdoDsn);
89+
$pdo->exec('DROP TABLE IF EXISTS lock_keys');
90+
8691
$key = new Key(uniqid(__METHOD__, true));
8792

8893
try {
@@ -100,9 +105,13 @@ public function testDsn(string $dsn, string $file = null)
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,10 +76,15 @@ public function testInvalidTtlConstruct()
7876
}
7977

8078
/**
79+
* @group integration
80+
*
8181
* @dataProvider provideDsn
8282
*/
8383
public function testDsn(string $dsn, string $file = null)
8484
{
85+
$pdo = new \PDO($dsn);
86+
$pdo->exec('DROP TABLE IF EXISTS lock_keys');
87+
8588
$key = new Key(uniqid(__METHOD__, true));
8689

8790
try {
@@ -99,7 +102,11 @@ public function testDsn(string $dsn, string $file = null)
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)