Completed
Push — master ( 644531...e26ed0 )
by Sergei
35s queued 15s
created

testForeignKeyConstraintViolationExceptionOnDelete()   B

Complexity

Conditions 6
Paths 15

Size

Total Lines 36
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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