Skip to content

Commit 01fcfad

Browse files
authored
Merge pull request #252 from Ocramius/fix/#251-dbal-queue-safe-queue-creation
Fix #251: dbal driver - safe queue creation
2 parents 9442bf4 + 78300cc commit 01fcfad

File tree

2 files changed

+91
-2
lines changed

2 files changed

+91
-2
lines changed

src/Bernard/Driver/DoctrineDriver.php

+21-2
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
@@ -34,12 +35,30 @@ public function listQueues()
3435

3536
/**
3637
* {@inheritDoc}
38+
*
39+
* @throws \Exception
3740
*/
3841
public function createQueue($queueName)
3942
{
4043
try {
41-
$this->connection->insert('bernard_queues', array('name' => $queueName));
42-
} catch (\Exception $e) {
44+
$this->connection->transactional(function () use ($queueName) {
45+
$queueExistsQb = $this->connection->createQueryBuilder();
46+
47+
$queueExists = $queueExistsQb
48+
->select('name')
49+
->from('bernard_queues')
50+
->where($queueExistsQb->expr()->eq('name', ':name'))
51+
->setParameter('name', $queueName)
52+
->execute();
53+
54+
if ($queueExists->fetch()) {
55+
// queue was already created
56+
return;
57+
}
58+
59+
$this->connection->insert('bernard_queues', array('name' => $queueName));
60+
});
61+
} catch (ConstraintViolationException $ignored) {
4362
// Because SQL server does not support a portable INSERT ON IGNORE syntax
4463
// this ignores error based on primary key.
4564
}

tests/Bernard/Tests/Driver/DoctrineDriverTest.php

+70
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,23 @@
55
use Bernard\Doctrine\MessagesSchema;
66
use Bernard\Driver\DoctrineDriver;
77

8+
use Doctrine\DBAL\Connection;
89
use Doctrine\DBAL\DriverManager;
10+
use Doctrine\DBAL\Logging\DebugStack;
911
use Doctrine\DBAL\Schema\Schema;
1012

1113
class DoctrineDriverTest extends \PHPUnit_Framework_TestCase
1214
{
15+
/**
16+
* @var Connection
17+
*/
18+
private $connection;
19+
20+
/**
21+
* @var DoctrineDriver
22+
*/
23+
private $driver;
24+
1325
public function setUp()
1426
{
1527
$this->connection = $this->setUpDatabase();
@@ -39,6 +51,64 @@ public function testCreateAndRemoveQueue()
3951
$this->assertEquals(array('send-newsletter'), $this->driver->listQueues());
4052
}
4153

54+
public function testCreateQueueWillNotAttemptDuplicateQueueCreation()
55+
{
56+
$logger = new DebugStack();
57+
58+
$this->connection->getConfiguration()->setSQLLogger($logger);
59+
60+
$this->driver->createQueue('import-users');
61+
$this->driver->createQueue('import-users');
62+
63+
self::assertCount(7, $logger->queries);
64+
self::assertStringMatchesFormat('%aSTART TRANSACTION%a', $logger->queries[1]['sql']);
65+
self::assertStringStartsWith('SELECT ', $logger->queries[2]['sql']);
66+
self::assertStringStartsWith('INSERT ', $logger->queries[3]['sql']);
67+
self::assertStringMatchesFormat('%aCOMMIT%a', $logger->queries[4]['sql']);
68+
self::assertStringMatchesFormat('%aSTART TRANSACTION%a', $logger->queries[5]['sql']);
69+
self::assertStringStartsWith('SELECT ', $logger->queries[6]['sql']);
70+
self::assertStringMatchesFormat('%aCOMMIT%a', $logger->queries[7]['sql']);
71+
}
72+
73+
public function testGenericExceptionsBubbleUpWhenThrownOnQueueCreation()
74+
{
75+
$connection = $this->getMockBuilder('Doctrine\DBAL\Connection')->disableOriginalConstructor()->getMock();
76+
$exception = new \Exception();
77+
$queryBuilder = $this->connection->createQueryBuilder();
78+
79+
$connection->expects(self::once())->method('transactional')->willReturnCallback('call_user_func');
80+
$connection->expects(self::once())->method('insert')->willThrowException($exception);
81+
$connection->expects(self::any())->method('createQueryBuilder')->willReturn($queryBuilder);
82+
83+
$driver = new DoctrineDriver($connection);
84+
85+
try {
86+
$driver->createQueue('foo');
87+
88+
self::fail('An exception was supposed to be thrown');
89+
} catch (\Exception $thrown) {
90+
self::assertSame($exception, $thrown);
91+
}
92+
}
93+
94+
public function testConstraintViolationExceptionsAreIgnored()
95+
{
96+
$connection = $this->getMockBuilder('Doctrine\DBAL\Connection')->disableOriginalConstructor()->getMock();
97+
$queryBuilder = $this->connection->createQueryBuilder();
98+
$exception = $this
99+
->getMockBuilder('Doctrine\DBAL\Exception\ConstraintViolationException')
100+
->disableOriginalConstructor()
101+
->getMock();
102+
103+
$connection->expects(self::once())->method('transactional')->willReturnCallback('call_user_func');
104+
$connection->expects(self::once())->method('insert')->willThrowException($exception);
105+
$connection->expects(self::any())->method('createQueryBuilder')->willReturn($queryBuilder);
106+
107+
$driver = new DoctrineDriver($connection);
108+
109+
$driver->createQueue('foo');
110+
}
111+
42112
public function testPushMessageLazilyCreatesQueue()
43113
{
44114
$this->driver->pushMessage('send-newsletter', 'something');

0 commit comments

Comments
 (0)