Completed
Pull Request — master (#3800)
by Benjamin
62:23
created

ExceptionTest::testNonUniqueFieldNameException()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
c 1
b 0
f 0
dl 0
loc 17
rs 9.9332
cc 2
nc 2
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 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
90
            $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
91
92
            $this->connection->insert('owning_table', ['id' => 2, 'constraint_id' => 2]);
93
        } finally {
94
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
95
        }
96
    }
97
98
    public function testForeignKeyConstraintViolationExceptionOnUpdate() : void
99
    {
100
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
101
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
102
        }
103
104
        $this->setUpForeignKeyConstraintViolationExceptionTest();
105
106
        try {
107
            $this->connection->insert('constraint_error_table', ['id' => 1]);
108
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
109
110
            $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
111
112
            $this->connection->update('constraint_error_table', ['id' => 2], ['id' => 1]);
113
        } finally {
114
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
115
        }
116
    }
117
118
    public function testForeignKeyConstraintViolationExceptionOnDelete() : void
119
    {
120
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
121
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
122
        }
123
124
        $this->setUpForeignKeyConstraintViolationExceptionTest();
125
126
        try {
127
            $this->connection->insert('constraint_error_table', ['id' => 1]);
128
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
129
130
            $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
131
132
            $this->connection->delete('constraint_error_table', ['id' => 1]);
133
        } finally {
134
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
135
        }
136
    }
137
138
    public function testForeignKeyConstraintViolationExceptionOnTruncate() : void
139
    {
140
        $platform = $this->connection->getDatabasePlatform();
141
142
        if (! $platform->supportsForeignKeyConstraints()) {
143
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
144
        }
145
146
        $this->setUpForeignKeyConstraintViolationExceptionTest();
147
148
        try {
149
            $this->connection->insert('constraint_error_table', ['id' => 1]);
150
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
151
152
            $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
153
154
            $this->connection->executeUpdate($platform->getTruncateTableSQL('constraint_error_table'));
155
        } finally {
156
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
157
        }
158
    }
159
160
    public function testNotNullConstraintViolationException() : void
161
    {
162
        $schema = new Schema();
163
164
        $table = $schema->createTable('notnull_table');
165
        $table->addColumn('id', 'integer', []);
166
        $table->addColumn('value', 'integer', ['notnull' => true]);
167
        $table->setPrimaryKey(['id']);
168
169
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
170
            $this->connection->exec($sql);
171
        }
172
173
        $this->expectException(Exception\NotNullConstraintViolationException::class);
174
        $this->connection->insert('notnull_table', ['id' => 1, 'value' => null]);
175
    }
176
177
    public function testInvalidFieldNameException() : void
178
    {
179
        $schema = new Schema();
180
181
        $table = $schema->createTable('bad_fieldname_table');
182
        $table->addColumn('id', 'integer', []);
183
184
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
185
            $this->connection->exec($sql);
186
        }
187
188
        $this->expectException(Exception\InvalidFieldNameException::class);
189
        $this->connection->insert('bad_fieldname_table', ['name' => 5]);
190
    }
191
192
    public function testNonUniqueFieldNameException() : void
193
    {
194
        $schema = new Schema();
195
196
        $table = $schema->createTable('ambiguous_list_table');
197
        $table->addColumn('id', 'integer');
198
199
        $table2 = $schema->createTable('ambiguous_list_table_2');
200
        $table2->addColumn('id', 'integer');
201
202
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
203
            $this->connection->exec($sql);
204
        }
205
206
        $sql = 'SELECT id FROM ambiguous_list_table, ambiguous_list_table_2';
207
        $this->expectException(Exception\NonUniqueFieldNameException::class);
208
        $this->connection->executeQuery($sql);
209
    }
210
211
    public function testUniqueConstraintViolationException() : void
212
    {
213
        $schema = new Schema();
214
215
        $table = $schema->createTable('unique_field_table');
216
        $table->addColumn('id', 'integer');
217
        $table->addUniqueIndex(['id']);
218
219
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
220
            $this->connection->exec($sql);
221
        }
222
223
        $this->connection->insert('unique_field_table', ['id' => 5]);
224
        $this->expectException(Exception\UniqueConstraintViolationException::class);
225
        $this->connection->insert('unique_field_table', ['id' => 5]);
226
    }
227
228
    public function testSyntaxErrorException() : void
229
    {
230
        $table = new Table('syntax_error_table');
231
        $table->addColumn('id', 'integer', []);
232
        $table->setPrimaryKey(['id']);
233
234
        $this->connection->getSchemaManager()->createTable($table);
235
236
        $sql = 'SELECT id FRO syntax_error_table';
237
        $this->expectException(Exception\SyntaxErrorException::class);
238
        $this->connection->executeQuery($sql);
239
    }
240
241
    public function testConnectionExceptionSqLite() : void
242
    {
243
        if (! ($this->connection->getDatabasePlatform() instanceof SqlitePlatform)) {
244
            $this->markTestSkipped('Only fails this way on sqlite');
245
        }
246
247
        // mode 0 is considered read-only on Windows
248
        $mode = PHP_OS === 'Linux' ? 0444 : 0000;
249
250
        $filename = sprintf('%s/%s', sys_get_temp_dir(), 'doctrine_failed_connection_' . $mode . '.db');
251
252
        if (file_exists($filename)) {
253
            $this->cleanupReadOnlyFile($filename);
254
        }
255
256
        touch($filename);
257
        chmod($filename, $mode);
258
259
        if ($this->isLinuxRoot()) {
260
            exec(sprintf('chattr +i %s', $filename));
261
        }
262
263
        $params = [
264
            'driver' => 'pdo_sqlite',
265
            'path'   => $filename,
266
        ];
267
        $conn   = DriverManager::getConnection($params);
268
269
        $schema = new Schema();
270
        $table  = $schema->createTable('no_connection');
271
        $table->addColumn('id', 'integer');
272
273
        $this->expectException(Exception\ReadOnlyException::class);
274
        $this->expectExceptionMessage(
275
            <<<EOT
276
An exception occurred while executing "CREATE TABLE no_connection (id INTEGER NOT NULL)":
277
278
SQLSTATE[HY000]: General error: 8 attempt to write a readonly database
279
EOT
280
        );
281
282
        try {
283
            foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
284
                $conn->exec($sql);
285
            }
286
        } finally {
287
            $this->cleanupReadOnlyFile($filename);
288
        }
289
    }
290
291
    /**
292
     * @param array<string, mixed> $params
293
     *
294
     * @dataProvider getConnectionParams
295
     */
296
    public function testConnectionException(array $params) : void
297
    {
298
        $platform = $this->connection->getDatabasePlatform();
299
300
        if ($platform instanceof SqlitePlatform) {
301
            $this->markTestSkipped('Only skipped if platform is not sqlite');
302
        }
303
304
        if ($platform instanceof PostgreSqlPlatform && isset($params['password'])) {
305
            $this->markTestSkipped('Does not work on Travis');
306
        }
307
308
        if ($platform instanceof MySqlPlatform && isset($params['user'])) {
309
            $wrappedConnection = $this->connection->getWrappedConnection();
310
            assert($wrappedConnection instanceof ServerInfoAwareConnection);
311
312
            if (version_compare($wrappedConnection->getServerVersion(), '8', '>=')) {
313
                $this->markTestIncomplete('PHP currently does not completely support MySQL 8');
314
            }
315
        }
316
317
        $defaultParams = $this->connection->getParams();
318
        $params        = array_merge($defaultParams, $params);
319
320
        $conn = DriverManager::getConnection($params);
321
322
        $schema = new Schema();
323
        $table  = $schema->createTable('no_connection');
324
        $table->addColumn('id', 'integer');
325
326
        $this->expectException(Exception\ConnectionException::class);
327
328
        foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
329
            $conn->exec($sql);
330
        }
331
    }
332
333
    /**
334
     * @return array<int, array<int, mixed>>
335
     */
336
    public static function getConnectionParams() : iterable
337
    {
338
        return [
339
            [['user' => 'not_existing']],
340
            [['password' => 'really_not']],
341
            [['host' => 'localnope']],
342
        ];
343
    }
344
345
    private function setUpForeignKeyConstraintViolationExceptionTest() : void
346
    {
347
        $schemaManager = $this->connection->getSchemaManager();
348
349
        $table = new Table('constraint_error_table');
350
        $table->addColumn('id', 'integer', []);
351
        $table->setPrimaryKey(['id']);
352
353
        $owningTable = new Table('owning_table');
354
        $owningTable->addColumn('id', 'integer', []);
355
        $owningTable->addColumn('constraint_id', 'integer', []);
356
        $owningTable->setPrimaryKey(['id']);
357
        $owningTable->addForeignKeyConstraint($table, ['constraint_id'], ['id']);
358
359
        $schemaManager->createTable($table);
360
        $schemaManager->createTable($owningTable);
361
    }
362
363
    private function tearDownForeignKeyConstraintViolationExceptionTest() : void
364
    {
365
        $schemaManager = $this->connection->getSchemaManager();
366
367
        $schemaManager->dropTable('owning_table');
368
        $schemaManager->dropTable('constraint_error_table');
369
    }
370
371
    private function isLinuxRoot() : bool
372
    {
373
        return PHP_OS === 'Linux' && posix_getpwuid(posix_geteuid())['name'] === 'root';
374
    }
375
376
    private function cleanupReadOnlyFile(string $filename) : void
377
    {
378
        if ($this->isLinuxRoot()) {
379
            exec(sprintf('chattr -i %s', $filename));
380
        }
381
382
        chmod($filename, 0200); // make the file writable again, so it can be removed on Windows
383
        unlink($filename);
384
    }
385
}
386