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