Skip to content

Commit d1a44e2

Browse files
committed
Merge remote-tracking branch 'ocramius/fix/#251-dbal-queue-safe-queue-creation' into doctrine-safe-queue-creation
2 parents bb4ad01 + 78300cc commit d1a44e2

File tree

2 files changed

+83
-5
lines changed

2 files changed

+83
-5
lines changed

src/Driver/DoctrineDriver.php

+20-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Bernard\Driver;
44

55
use Doctrine\DBAL\Connection;
6+
use Doctrine\DBAL\Exception\ConstraintViolationException;
67

78
/**
89
* Driver supporting Doctrine DBAL
@@ -33,13 +34,29 @@ public function listQueues()
3334
}
3435

3536
/**
36-
* {@inheritdoc}
37+
* {@inheritDoc}
3738
*/
3839
public function createQueue($queueName)
3940
{
4041
try {
41-
$this->connection->insert('bernard_queues', ['name' => $queueName]);
42-
} catch (\Exception $e) {
42+
$this->connection->transactional(function () use ($queueName) {
43+
$queueExistsQb = $this->connection->createQueryBuilder();
44+
45+
$queueExists = $queueExistsQb
46+
->select('name')
47+
->from('bernard_queues')
48+
->where($queueExistsQb->expr()->eq('name', ':name'))
49+
->setParameter('name', $queueName)
50+
->execute();
51+
52+
if ($queueExists->fetch()) {
53+
// queue was already created
54+
return;
55+
}
56+
57+
$this->connection->insert('bernard_queues', array('name' => $queueName));
58+
});
59+
} catch (ConstraintViolationException $ignored) {
4360
// Because SQL server does not support a portable INSERT ON IGNORE syntax
4461
// this ignores error based on primary key.
4562
}
@@ -100,7 +117,6 @@ public function popMessage($queueName, $duration = 5)
100117
usleep(10000);
101118
}
102119
}
103-
104120
/**
105121
* {@inheritdoc}
106122
*/

tests/Driver/AbstractDoctrineDriverTest.php

+63-1
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
use Bernard\Doctrine\MessagesSchema;
66
use Bernard\Driver\DoctrineDriver;
77
use Doctrine\DBAL\Platforms\MySqlPlatform;
8+
9+
use Doctrine\DBAL\Connection;
10+
use Doctrine\DBAL\DriverManager;
11+
use Doctrine\DBAL\Logging\DebugStack;
812
use Doctrine\DBAL\Schema\Schema;
913

1014
abstract class AbstractDoctrineDriverTest extends \PHPUnit_Framework_TestCase
1115
{
1216
/**
13-
* @var \Doctrine\DBAL\Connection
17+
* @var Connection
1418
*/
1519
private $connection;
1620

@@ -72,6 +76,64 @@ public function testCreateAndRemoveQueue()
7276
$this->assertEquals(array('send-newsletter'), $this->driver->listQueues());
7377
}
7478

79+
public function testCreateQueueWillNotAttemptDuplicateQueueCreation()
80+
{
81+
$logger = new DebugStack();
82+
83+
$this->connection->getConfiguration()->setSQLLogger($logger);
84+
85+
$this->driver->createQueue('import-users');
86+
$this->driver->createQueue('import-users');
87+
88+
self::assertCount(7, $logger->queries);
89+
self::assertStringMatchesFormat('%aSTART TRANSACTION%a', $logger->queries[1]['sql']);
90+
self::assertStringStartsWith('SELECT ', $logger->queries[2]['sql']);
91+
self::assertStringStartsWith('INSERT ', $logger->queries[3]['sql']);
92+
self::assertStringMatchesFormat('%aCOMMIT%a', $logger->queries[4]['sql']);
93+
self::assertStringMatchesFormat('%aSTART TRANSACTION%a', $logger->queries[5]['sql']);
94+
self::assertStringStartsWith('SELECT ', $logger->queries[6]['sql']);
95+
self::assertStringMatchesFormat('%aCOMMIT%a', $logger->queries[7]['sql']);
96+
}
97+
98+
public function testGenericExceptionsBubbleUpWhenThrownOnQueueCreation()
99+
{
100+
$connection = $this->getMockBuilder('Doctrine\DBAL\Connection')->disableOriginalConstructor()->getMock();
101+
$exception = new \Exception();
102+
$queryBuilder = $this->connection->createQueryBuilder();
103+
104+
$connection->expects(self::once())->method('transactional')->willReturnCallback('call_user_func');
105+
$connection->expects(self::once())->method('insert')->willThrowException($exception);
106+
$connection->expects(self::any())->method('createQueryBuilder')->willReturn($queryBuilder);
107+
108+
$driver = new DoctrineDriver($connection);
109+
110+
try {
111+
$driver->createQueue('foo');
112+
113+
self::fail('An exception was supposed to be thrown');
114+
} catch (\Exception $thrown) {
115+
self::assertSame($exception, $thrown);
116+
}
117+
}
118+
119+
public function testConstraintViolationExceptionsAreIgnored()
120+
{
121+
$connection = $this->getMockBuilder('Doctrine\DBAL\Connection')->disableOriginalConstructor()->getMock();
122+
$queryBuilder = $this->connection->createQueryBuilder();
123+
$exception = $this
124+
->getMockBuilder('Doctrine\DBAL\Exception\ConstraintViolationException')
125+
->disableOriginalConstructor()
126+
->getMock();
127+
128+
$connection->expects(self::once())->method('transactional')->willReturnCallback('call_user_func');
129+
$connection->expects(self::once())->method('insert')->willThrowException($exception);
130+
$connection->expects(self::any())->method('createQueryBuilder')->willReturn($queryBuilder);
131+
132+
$driver = new DoctrineDriver($connection);
133+
134+
$driver->createQueue('foo');
135+
}
136+
75137
public function testPushMessageLazilyCreatesQueue()
76138
{
77139
$this->driver->pushMessage('send-newsletter', 'something');

0 commit comments

Comments
 (0)