Completed
Pull Request — master (#2836)
by
unknown
63:43
created

ExceptionTest::testConnectionException()   B

Complexity

Conditions 9
Paths 48

Size

Total Lines 38
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 21
c 1
b 0
f 0
dl 0
loc 38
rs 8.0555
cc 9
nc 48
nop 1
1
<?php
2
3
namespace Doctrine\Tests\DBAL\Functional;
4
5
use Doctrine\Common\EventManager;
6
use Doctrine\DBAL\Driver\ExceptionConverterDriver;
7
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
8
use Doctrine\DBAL\DriverManager;
9
use Doctrine\DBAL\Exception;
10
use Doctrine\DBAL\Event\Listeners\SqliteSessionInit;
11
use Doctrine\DBAL\Platforms\DrizzlePlatform;
12
use Doctrine\DBAL\Platforms\MySqlPlatform;
13
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
14
use Doctrine\DBAL\Platforms\SqlitePlatform;
15
use Doctrine\DBAL\Schema\Schema;
16
use Doctrine\DBAL\Schema\Table;
17
use Doctrine\Tests\DbalFunctionalTestCase;
18
use Throwable;
19
use const PHP_OS;
20
use function array_merge;
21
use function assert;
22
use function chmod;
23
use function exec;
24
use function file_exists;
25
use function posix_geteuid;
26
use function posix_getpwuid;
27
use function sprintf;
28
use function sys_get_temp_dir;
29
use function touch;
30
use function unlink;
31
use function version_compare;
32
33
class ExceptionTest extends DbalFunctionalTestCase
34
{
35
    protected function setUp() : void
36
    {
37
        parent::setUp();
38
39
        if ($this->connection->getDriver() instanceof ExceptionConverterDriver) {
40
            return;
41
        }
42
43
        $this->markTestSkipped('Driver does not support special exception handling.');
44
    }
45
46
    public function testPrimaryConstraintViolationException() : void
47
    {
48
        $table = new Table('duplicatekey_table');
49
        $table->addColumn('id', 'integer', []);
50
        $table->setPrimaryKey(['id']);
51
52
        $this->connection->getSchemaManager()->createTable($table);
53
54
        $this->connection->insert('duplicatekey_table', ['id' => 1]);
55
56
        $this->expectException(Exception\UniqueConstraintViolationException::class);
57
        $this->connection->insert('duplicatekey_table', ['id' => 1]);
58
    }
59
60
    public function testTableNotFoundException() : void
61
    {
62
        $sql = 'SELECT * FROM unknown_table';
63
64
        $this->expectException(Exception\TableNotFoundException::class);
65
        $this->connection->executeQuery($sql);
66
    }
67
68
    public function testTableExistsException() : void
69
    {
70
        $schemaManager = $this->connection->getSchemaManager();
71
        $table         = new Table('alreadyexist_table');
72
        $table->addColumn('id', 'integer', []);
73
        $table->setPrimaryKey(['id']);
74
75
        $this->expectException(Exception\TableExistsException::class);
76
        $schemaManager->createTable($table);
77
        $schemaManager->createTable($table);
78
    }
79
80
    public function testForeignKeyConstraintViolationExceptionOnInsert() : void
81
    {
82
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
83
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
84
        }
85
86
        $this->setUpForeignKeyConstraintViolationExceptionTest();
87
88
        try {
89
            $this->connection->insert('constraint_error_table', ['id' => 1]);
90
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
91
        } catch (Throwable $exception) {
92
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
93
94
            throw $exception;
95
        }
96
97
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
98
99
        try {
100
            $this->connection->insert('owning_table', ['id' => 2, 'constraint_id' => 2]);
101
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
102
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
103
104
            throw $exception;
105
        } catch (Throwable $exception) {
106
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
107
108
            throw $exception;
109
        }
110
111
        $this->tearDownForeignKeyConstraintViolationExceptionTest();
112
    }
113
114
    public function testSqliteForeignKeyConstraintViolationExceptionOnInsert() : void
115
    {
116
        if (!($this->connection->getDatabasePlatform() instanceof SqlitePlatform)) {
117
            $this->markTestSkipped('Only fails this way on sqlite');
118
        }
119
120
        $tmpFilename = sprintf('%s/%s', sys_get_temp_dir(), 'doctrine_sqlite_foreign_key_constraint_violation.db');
121
        
122
        if (file_exists($tmpFilename)) {
123
            $this->cleanupReadOnlyFile($tmpFilename);
124
        }
125
126
        $evm = new EventManager();
127
        $evm->addEventSubscriber(new SqliteSessionInit());
128
129
        $connection = DriverManager::getConnection([
130
            'driver' => 'pdo_sqlite',
131
            'path'   => $tmpFilename,
132
        ], null, $evm);
133
134
        $schemaManager = $connection->getSchemaManager();
135
136
        $table = new Table('constraint_error_table');
137
        $table->addColumn('id', 'integer', []);
138
        $table->setPrimaryKey(['id']);
139
140
        $owningTable = new Table('owning_table');
141
        $owningTable->addColumn('id', 'integer', []);
142
        $owningTable->addColumn('constraint_id', 'integer', []);
143
        $owningTable->setPrimaryKey(['id']);
144
        $owningTable->addForeignKeyConstraint($table, ['constraint_id'], ['id']);
145
146
        $schemaManager->createTable($table);
147
        $schemaManager->createTable($owningTable);
148
149
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
150
151
        try {
152
            $connection->insert('constraint_error_table', ['id' => 1]);
153
            $connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
154
            $connection->insert('owning_table', ['id' => 2, 'constraint_id' => 2]);
155
        } finally {
156
            $this->cleanupReadOnlyFile($tmpFilename);
157
        }
158
    }
159
160
    public function testForeignKeyConstraintViolationExceptionOnUpdate() : void
161
    {
162
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
163
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
164
        }
165
166
        $this->setUpForeignKeyConstraintViolationExceptionTest();
167
168
        try {
169
            $this->connection->insert('constraint_error_table', ['id' => 1]);
170
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
171
        } catch (Throwable $exception) {
172
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
173
174
            throw $exception;
175
        }
176
177
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
178
179
        try {
180
            $this->connection->update('constraint_error_table', ['id' => 2], ['id' => 1]);
181
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
182
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
183
184
            throw $exception;
185
        } catch (Throwable $exception) {
186
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
187
188
            throw $exception;
189
        }
190
191
        $this->tearDownForeignKeyConstraintViolationExceptionTest();
192
    }
193
194
    public function testForeignKeyConstraintViolationExceptionOnDelete() : void
195
    {
196
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
197
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
198
        }
199
200
        $this->setUpForeignKeyConstraintViolationExceptionTest();
201
202
        try {
203
            $this->connection->insert('constraint_error_table', ['id' => 1]);
204
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
205
        } catch (Throwable $exception) {
206
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
207
208
            throw $exception;
209
        }
210
211
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
212
213
        try {
214
            $this->connection->delete('constraint_error_table', ['id' => 1]);
215
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
216
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
217
218
            throw $exception;
219
        } catch (Throwable $exception) {
220
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
221
222
            throw $exception;
223
        }
224
225
        $this->tearDownForeignKeyConstraintViolationExceptionTest();
226
    }
227
228
    public function testForeignKeyConstraintViolationExceptionOnTruncate() : void
229
    {
230
        $platform = $this->connection->getDatabasePlatform();
231
232
        if (! $platform->supportsForeignKeyConstraints()) {
233
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
234
        }
235
236
        $this->setUpForeignKeyConstraintViolationExceptionTest();
237
238
        try {
239
            $this->connection->insert('constraint_error_table', ['id' => 1]);
240
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
241
        } catch (Throwable $exception) {
242
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
243
244
            throw $exception;
245
        }
246
247
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
248
249
        try {
250
            $this->connection->executeUpdate($platform->getTruncateTableSQL('constraint_error_table'));
251
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
252
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
253
254
            throw $exception;
255
        } catch (Throwable $exception) {
256
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
257
258
            throw $exception;
259
        }
260
261
        $this->tearDownForeignKeyConstraintViolationExceptionTest();
262
    }
263
264
    public function testNotNullConstraintViolationException() : void
265
    {
266
        $schema = new Schema();
267
268
        $table = $schema->createTable('notnull_table');
269
        $table->addColumn('id', 'integer', []);
270
        $table->addColumn('value', 'integer', ['notnull' => true]);
271
        $table->setPrimaryKey(['id']);
272
273
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
274
            $this->connection->exec($sql);
275
        }
276
277
        $this->expectException(Exception\NotNullConstraintViolationException::class);
278
        $this->connection->insert('notnull_table', ['id' => 1, 'value' => null]);
279
    }
280
281
    public function testInvalidFieldNameException() : void
282
    {
283
        $schema = new Schema();
284
285
        $table = $schema->createTable('bad_fieldname_table');
286
        $table->addColumn('id', 'integer', []);
287
288
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
289
            $this->connection->exec($sql);
290
        }
291
292
        $this->expectException(Exception\InvalidFieldNameException::class);
293
        $this->connection->insert('bad_fieldname_table', ['name' => 5]);
294
    }
295
296
    public function testNonUniqueFieldNameException() : void
297
    {
298
        $schema = new Schema();
299
300
        $table = $schema->createTable('ambiguous_list_table');
301
        $table->addColumn('id', 'integer');
302
303
        $table2 = $schema->createTable('ambiguous_list_table_2');
304
        $table2->addColumn('id', 'integer');
305
306
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
307
            $this->connection->exec($sql);
308
        }
309
310
        $sql = 'SELECT id FROM ambiguous_list_table, ambiguous_list_table_2';
311
        $this->expectException(Exception\NonUniqueFieldNameException::class);
312
        $this->connection->executeQuery($sql);
313
    }
314
315
    public function testUniqueConstraintViolationException() : void
316
    {
317
        $schema = new Schema();
318
319
        $table = $schema->createTable('unique_field_table');
320
        $table->addColumn('id', 'integer');
321
        $table->addUniqueIndex(['id']);
322
323
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
324
            $this->connection->exec($sql);
325
        }
326
327
        $this->connection->insert('unique_field_table', ['id' => 5]);
328
        $this->expectException(Exception\UniqueConstraintViolationException::class);
329
        $this->connection->insert('unique_field_table', ['id' => 5]);
330
    }
331
332
    public function testSyntaxErrorException() : void
333
    {
334
        $table = new Table('syntax_error_table');
335
        $table->addColumn('id', 'integer', []);
336
        $table->setPrimaryKey(['id']);
337
338
        $this->connection->getSchemaManager()->createTable($table);
339
340
        $sql = 'SELECT id FRO syntax_error_table';
341
        $this->expectException(Exception\SyntaxErrorException::class);
342
        $this->connection->executeQuery($sql);
343
    }
344
345
    public function testConnectionExceptionSqLite() : void
346
    {
347
        if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) {
348
            $this->markTestSkipped('Only fails this way on sqlite');
349
        }
350
351
        // mode 0 is considered read-only on Windows
352
        $mode = PHP_OS === 'Linux' ? 0444 : 0000;
353
354
        $filename = sprintf('%s/%s', sys_get_temp_dir(), 'doctrine_failed_connection_' . $mode . '.db');
355
356
        if (file_exists($filename)) {
357
            $this->cleanupReadOnlyFile($filename);
358
        }
359
360
        touch($filename);
361
        chmod($filename, $mode);
362
363
        if ($this->isLinuxRoot()) {
364
            exec(sprintf('chattr +i %s', $filename));
365
        }
366
367
        $params = [
368
            'driver' => 'pdo_sqlite',
369
            'path'   => $filename,
370
        ];
371
        $conn   = DriverManager::getConnection($params);
372
373
        $schema = new Schema();
374
        $table  = $schema->createTable('no_connection');
375
        $table->addColumn('id', 'integer');
376
377
        $this->expectException(Exception\ReadOnlyException::class);
378
        $this->expectExceptionMessage(
379
            <<<EOT
380
An exception occurred while executing 'CREATE TABLE no_connection (id INTEGER NOT NULL)':
381
382
SQLSTATE[HY000]: General error: 8 attempt to write a readonly database
383
EOT
384
        );
385
386
        try {
387
            foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
388
                $conn->exec($sql);
389
            }
390
        } finally {
391
            $this->cleanupReadOnlyFile($filename);
392
        }
393
    }
394
395
    /**
396
     * @param array<string, mixed> $params
397
     *
398
     * @dataProvider getConnectionParams
399
     */
400
    public function testConnectionException(array $params) : void
401
    {
402
        $platform = $this->connection->getDatabasePlatform();
403
404
        if ($platform instanceof SqlitePlatform) {
405
            $this->markTestSkipped('Only skipped if platform is not sqlite');
406
        }
407
408
        if ($platform instanceof DrizzlePlatform) {
409
            $this->markTestSkipped('Drizzle does not always support authentication');
410
        }
411
412
        if ($platform instanceof PostgreSqlPlatform && isset($params['password'])) {
413
            $this->markTestSkipped('Does not work on Travis');
414
        }
415
416
        if ($platform instanceof MySqlPlatform && isset($params['user'])) {
417
            $wrappedConnection = $this->connection->getWrappedConnection();
418
            assert($wrappedConnection instanceof ServerInfoAwareConnection);
419
420
            if (version_compare($wrappedConnection->getServerVersion(), '8', '>=')) {
421
                $this->markTestIncomplete('PHP currently does not completely support MySQL 8');
422
            }
423
        }
424
425
        $defaultParams = $this->connection->getParams();
426
        $params        = array_merge($defaultParams, $params);
427
428
        $conn = DriverManager::getConnection($params);
429
430
        $schema = new Schema();
431
        $table  = $schema->createTable('no_connection');
432
        $table->addColumn('id', 'integer');
433
434
        $this->expectException(Exception\ConnectionException::class);
435
436
        foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
437
            $conn->exec($sql);
438
        }
439
    }
440
441
    /**
442
     * @return array<int, array<int, mixed>>
443
     */
444
    public static function getConnectionParams() : iterable
445
    {
446
        return [
447
            [['user' => 'not_existing']],
448
            [['password' => 'really_not']],
449
            [['host' => 'localnope']],
450
        ];
451
    }
452
453
    private function setUpForeignKeyConstraintViolationExceptionTest() : void
454
    {
455
        $schemaManager = $this->connection->getSchemaManager();
456
457
        $table = new Table('constraint_error_table');
458
        $table->addColumn('id', 'integer', []);
459
        $table->setPrimaryKey(['id']);
460
461
        $owningTable = new Table('owning_table');
462
        $owningTable->addColumn('id', 'integer', []);
463
        $owningTable->addColumn('constraint_id', 'integer', []);
464
        $owningTable->setPrimaryKey(['id']);
465
        $owningTable->addForeignKeyConstraint($table, ['constraint_id'], ['id']);
466
467
        $schemaManager->createTable($table);
468
        $schemaManager->createTable($owningTable);
469
    }
470
471
    private function tearDownForeignKeyConstraintViolationExceptionTest() : void
472
    {
473
        $schemaManager = $this->connection->getSchemaManager();
474
475
        $schemaManager->dropTable('owning_table');
476
        $schemaManager->dropTable('constraint_error_table');
477
    }
478
479
    private function isLinuxRoot() : bool
480
    {
481
        return PHP_OS === 'Linux' && posix_getpwuid(posix_geteuid())['name'] === 'root';
482
    }
483
484
    private function cleanupReadOnlyFile(string $filename) : void
485
    {
486
        if ($this->isLinuxRoot()) {
487
            exec(sprintf('chattr -i %s', $filename));
488
        }
489
490
        chmod($filename, 0200); // make the file writable again, so it can be removed on Windows
491
        unlink($filename);
492
    }
493
}
494