ExceptionTest.php 14.9 KB
Newer Older
1
<?php
Sergei Morozov's avatar
Sergei Morozov committed
2

3 4
namespace Doctrine\Tests\DBAL\Functional;

5
use Doctrine\DBAL\Driver\ExceptionConverterDriver;
6
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
Sergei Morozov's avatar
Sergei Morozov committed
7
use Doctrine\DBAL\DriverManager;
Luís Cobucci's avatar
Luís Cobucci committed
8
use Doctrine\DBAL\Exception;
9 10 11 12
use Doctrine\DBAL\Platforms\DrizzlePlatform;
use Doctrine\DBAL\Platforms\MySqlPlatform;
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
Sergei Morozov's avatar
Sergei Morozov committed
13
use Doctrine\DBAL\Schema\Schema;
14
use Doctrine\DBAL\Schema\Table;
Sergei Morozov's avatar
Sergei Morozov committed
15 16
use Doctrine\Tests\DbalFunctionalTestCase;
use Throwable;
17
use function array_merge;
18
use function assert;
19
use function chmod;
20
use function exec;
21
use function file_exists;
22 23
use function posix_geteuid;
use function posix_getpwuid;
24 25 26 27
use function sprintf;
use function sys_get_temp_dir;
use function touch;
use function unlink;
28
use function version_compare;
Grégoire Paris's avatar
Grégoire Paris committed
29
use const PHP_OS;
30

Sergei Morozov's avatar
Sergei Morozov committed
31
class ExceptionTest extends DbalFunctionalTestCase
32
{
33
    protected function setUp() : void
34 35 36
    {
        parent::setUp();

Sergei Morozov's avatar
Sergei Morozov committed
37
        if ($this->connection->getDriver() instanceof ExceptionConverterDriver) {
Sergei Morozov's avatar
Sergei Morozov committed
38
            return;
39
        }
Sergei Morozov's avatar
Sergei Morozov committed
40 41

        $this->markTestSkipped('Driver does not support special exception handling.');
42 43
    }

44
    public function testPrimaryConstraintViolationException() : void
45
    {
Sergei Morozov's avatar
Sergei Morozov committed
46 47 48
        $table = new Table('duplicatekey_table');
        $table->addColumn('id', 'integer', []);
        $table->setPrimaryKey(['id']);
49

Sergei Morozov's avatar
Sergei Morozov committed
50
        $this->connection->getSchemaManager()->createTable($table);
51

Sergei Morozov's avatar
Sergei Morozov committed
52
        $this->connection->insert('duplicatekey_table', ['id' => 1]);
53

Luís Cobucci's avatar
Luís Cobucci committed
54
        $this->expectException(Exception\UniqueConstraintViolationException::class);
Sergei Morozov's avatar
Sergei Morozov committed
55
        $this->connection->insert('duplicatekey_table', ['id' => 1]);
56
    }
57

58
    public function testTableNotFoundException() : void
59
    {
Sergei Morozov's avatar
Sergei Morozov committed
60
        $sql = 'SELECT * FROM unknown_table';
61

Luís Cobucci's avatar
Luís Cobucci committed
62
        $this->expectException(Exception\TableNotFoundException::class);
Sergei Morozov's avatar
Sergei Morozov committed
63
        $this->connection->executeQuery($sql);
64
    }
65

66
    public function testTableExistsException() : void
67
    {
Sergei Morozov's avatar
Sergei Morozov committed
68
        $schemaManager = $this->connection->getSchemaManager();
Sergei Morozov's avatar
Sergei Morozov committed
69 70 71
        $table         = new Table('alreadyexist_table');
        $table->addColumn('id', 'integer', []);
        $table->setPrimaryKey(['id']);
72

Luís Cobucci's avatar
Luís Cobucci committed
73
        $this->expectException(Exception\TableExistsException::class);
74 75
        $schemaManager->createTable($table);
        $schemaManager->createTable($table);
76
    }
77

78
    public function testForeignKeyConstraintViolationExceptionOnInsert() : void
79
    {
80 81
        if ($this->connection->getDatabasePlatform()->getName() === 'sqlite') {
            $this->connection->exec('PRAGMA foreign_keys=ON');
82 83
        }

84
        $this->setUpForeignKeyConstraintViolationExceptionTest();
85

86
        try {
Sergei Morozov's avatar
Sergei Morozov committed
87 88
            $this->connection->insert('constraint_error_table', ['id' => 1]);
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
Sergei Morozov's avatar
Sergei Morozov committed
89
        } catch (Throwable $exception) {
90 91 92 93
            $this->tearDownForeignKeyConstraintViolationExceptionTest();

            throw $exception;
        }
94

Luís Cobucci's avatar
Luís Cobucci committed
95
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
96 97

        try {
Sergei Morozov's avatar
Sergei Morozov committed
98
            $this->connection->insert('owning_table', ['id' => 2, 'constraint_id' => 2]);
Luís Cobucci's avatar
Luís Cobucci committed
99
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
100 101
            $this->tearDownForeignKeyConstraintViolationExceptionTest();

102
            throw $exception;
Sergei Morozov's avatar
Sergei Morozov committed
103
        } catch (Throwable $exception) {
104 105
            $this->tearDownForeignKeyConstraintViolationExceptionTest();

106 107
            throw $exception;
        }
108

109 110 111
        $this->tearDownForeignKeyConstraintViolationExceptionTest();
    }

112
    public function testForeignKeyConstraintViolationExceptionOnUpdate() : void
113
    {
Sergei Morozov's avatar
Sergei Morozov committed
114
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
Sergei Morozov's avatar
Sergei Morozov committed
115
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
116 117
        }

118 119
        $this->setUpForeignKeyConstraintViolationExceptionTest();

120
        try {
Sergei Morozov's avatar
Sergei Morozov committed
121 122
            $this->connection->insert('constraint_error_table', ['id' => 1]);
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
Sergei Morozov's avatar
Sergei Morozov committed
123
        } catch (Throwable $exception) {
124 125 126 127
            $this->tearDownForeignKeyConstraintViolationExceptionTest();

            throw $exception;
        }
128

Luís Cobucci's avatar
Luís Cobucci committed
129
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
130 131

        try {
Sergei Morozov's avatar
Sergei Morozov committed
132
            $this->connection->update('constraint_error_table', ['id' => 2], ['id' => 1]);
Luís Cobucci's avatar
Luís Cobucci committed
133
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
134 135
            $this->tearDownForeignKeyConstraintViolationExceptionTest();

136
            throw $exception;
Sergei Morozov's avatar
Sergei Morozov committed
137
        } catch (Throwable $exception) {
138 139
            $this->tearDownForeignKeyConstraintViolationExceptionTest();

140 141
            throw $exception;
        }
142 143 144 145

        $this->tearDownForeignKeyConstraintViolationExceptionTest();
    }

146
    public function testForeignKeyConstraintViolationExceptionOnDelete() : void
147
    {
Sergei Morozov's avatar
Sergei Morozov committed
148
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
Sergei Morozov's avatar
Sergei Morozov committed
149
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
150 151 152 153
        }

        $this->setUpForeignKeyConstraintViolationExceptionTest();

154
        try {
Sergei Morozov's avatar
Sergei Morozov committed
155 156
            $this->connection->insert('constraint_error_table', ['id' => 1]);
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
Sergei Morozov's avatar
Sergei Morozov committed
157
        } catch (Throwable $exception) {
158 159 160 161
            $this->tearDownForeignKeyConstraintViolationExceptionTest();

            throw $exception;
        }
162

Luís Cobucci's avatar
Luís Cobucci committed
163
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
164 165

        try {
Sergei Morozov's avatar
Sergei Morozov committed
166
            $this->connection->delete('constraint_error_table', ['id' => 1]);
Luís Cobucci's avatar
Luís Cobucci committed
167
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
168 169
            $this->tearDownForeignKeyConstraintViolationExceptionTest();

170
            throw $exception;
Sergei Morozov's avatar
Sergei Morozov committed
171
        } catch (Throwable $exception) {
172 173
            $this->tearDownForeignKeyConstraintViolationExceptionTest();

174 175
            throw $exception;
        }
176 177 178 179

        $this->tearDownForeignKeyConstraintViolationExceptionTest();
    }

180
    public function testForeignKeyConstraintViolationExceptionOnTruncate() : void
181
    {
Sergei Morozov's avatar
Sergei Morozov committed
182
        $platform = $this->connection->getDatabasePlatform();
183

Sergei Morozov's avatar
Sergei Morozov committed
184 185
        if (! $platform->supportsForeignKeyConstraints()) {
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
186 187 188 189
        }

        $this->setUpForeignKeyConstraintViolationExceptionTest();

190
        try {
Sergei Morozov's avatar
Sergei Morozov committed
191 192
            $this->connection->insert('constraint_error_table', ['id' => 1]);
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
Sergei Morozov's avatar
Sergei Morozov committed
193
        } catch (Throwable $exception) {
194 195 196 197
            $this->tearDownForeignKeyConstraintViolationExceptionTest();

            throw $exception;
        }
198

Luís Cobucci's avatar
Luís Cobucci committed
199
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
200 201

        try {
Sergei Morozov's avatar
Sergei Morozov committed
202
            $this->connection->executeUpdate($platform->getTruncateTableSQL('constraint_error_table'));
Luís Cobucci's avatar
Luís Cobucci committed
203
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
204 205
            $this->tearDownForeignKeyConstraintViolationExceptionTest();

206
            throw $exception;
Sergei Morozov's avatar
Sergei Morozov committed
207
        } catch (Throwable $exception) {
208 209
            $this->tearDownForeignKeyConstraintViolationExceptionTest();

210 211
            throw $exception;
        }
212 213

        $this->tearDownForeignKeyConstraintViolationExceptionTest();
214
    }
215

216
    public function testNotNullConstraintViolationException() : void
217
    {
Sergei Morozov's avatar
Sergei Morozov committed
218
        $schema = new Schema();
219

Sergei Morozov's avatar
Sergei Morozov committed
220 221 222 223
        $table = $schema->createTable('notnull_table');
        $table->addColumn('id', 'integer', []);
        $table->addColumn('value', 'integer', ['notnull' => true]);
        $table->setPrimaryKey(['id']);
224

Sergei Morozov's avatar
Sergei Morozov committed
225 226
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
            $this->connection->exec($sql);
227 228
        }

Luís Cobucci's avatar
Luís Cobucci committed
229
        $this->expectException(Exception\NotNullConstraintViolationException::class);
Sergei Morozov's avatar
Sergei Morozov committed
230
        $this->connection->insert('notnull_table', ['id' => 1, 'value' => null]);
231
    }
232

233
    public function testInvalidFieldNameException() : void
234
    {
Sergei Morozov's avatar
Sergei Morozov committed
235
        $schema = new Schema();
236

Sergei Morozov's avatar
Sergei Morozov committed
237 238
        $table = $schema->createTable('bad_fieldname_table');
        $table->addColumn('id', 'integer', []);
239

Sergei Morozov's avatar
Sergei Morozov committed
240 241
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
            $this->connection->exec($sql);
242 243
        }

Luís Cobucci's avatar
Luís Cobucci committed
244
        $this->expectException(Exception\InvalidFieldNameException::class);
Sergei Morozov's avatar
Sergei Morozov committed
245
        $this->connection->insert('bad_fieldname_table', ['name' => 5]);
246 247
    }

248
    public function testNonUniqueFieldNameException() : void
249
    {
Sergei Morozov's avatar
Sergei Morozov committed
250
        $schema = new Schema();
251

Sergei Morozov's avatar
Sergei Morozov committed
252
        $table = $schema->createTable('ambiguous_list_table');
253 254
        $table->addColumn('id', 'integer');

Sergei Morozov's avatar
Sergei Morozov committed
255
        $table2 = $schema->createTable('ambiguous_list_table_2');
256 257
        $table2->addColumn('id', 'integer');

Sergei Morozov's avatar
Sergei Morozov committed
258 259
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
            $this->connection->exec($sql);
260 261 262
        }

        $sql = 'SELECT id FROM ambiguous_list_table, ambiguous_list_table_2';
Luís Cobucci's avatar
Luís Cobucci committed
263
        $this->expectException(Exception\NonUniqueFieldNameException::class);
Sergei Morozov's avatar
Sergei Morozov committed
264
        $this->connection->executeQuery($sql);
265 266
    }

267
    public function testUniqueConstraintViolationException() : void
268
    {
Sergei Morozov's avatar
Sergei Morozov committed
269
        $schema = new Schema();
270

Sergei Morozov's avatar
Sergei Morozov committed
271
        $table = $schema->createTable('unique_field_table');
272
        $table->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
273
        $table->addUniqueIndex(['id']);
274

Sergei Morozov's avatar
Sergei Morozov committed
275 276
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
            $this->connection->exec($sql);
277
        }
278

Sergei Morozov's avatar
Sergei Morozov committed
279
        $this->connection->insert('unique_field_table', ['id' => 5]);
Luís Cobucci's avatar
Luís Cobucci committed
280
        $this->expectException(Exception\UniqueConstraintViolationException::class);
Sergei Morozov's avatar
Sergei Morozov committed
281
        $this->connection->insert('unique_field_table', ['id' => 5]);
282 283
    }

284
    public function testSyntaxErrorException() : void
285
    {
Sergei Morozov's avatar
Sergei Morozov committed
286 287 288
        $table = new Table('syntax_error_table');
        $table->addColumn('id', 'integer', []);
        $table->setPrimaryKey(['id']);
289

Sergei Morozov's avatar
Sergei Morozov committed
290
        $this->connection->getSchemaManager()->createTable($table);
291 292

        $sql = 'SELECT id FRO syntax_error_table';
Luís Cobucci's avatar
Luís Cobucci committed
293
        $this->expectException(Exception\SyntaxErrorException::class);
Sergei Morozov's avatar
Sergei Morozov committed
294
        $this->connection->executeQuery($sql);
295 296
    }

297
    public function testConnectionExceptionSqLite() : void
298
    {
299
        if (! ($this->connection->getDatabasePlatform() instanceof SqlitePlatform)) {
Sergei Morozov's avatar
Sergei Morozov committed
300
            $this->markTestSkipped('Only fails this way on sqlite');
301 302
        }

303 304 305
        // mode 0 is considered read-only on Windows
        $mode = PHP_OS === 'Linux' ? 0444 : 0000;

Sergei Morozov's avatar
Sergei Morozov committed
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 313 314

        touch($filename);
        chmod($filename, $mode);

315 316 317 318
        if ($this->isLinuxRoot()) {
            exec(sprintf('chattr +i %s', $filename));
        }

Sergei Morozov's avatar
Sergei Morozov committed
319
        $params = [
320 321
            'driver' => 'pdo_sqlite',
            'path'   => $filename,
Sergei Morozov's avatar
Sergei Morozov committed
322 323
        ];
        $conn   = DriverManager::getConnection($params);
324

Sergei Morozov's avatar
Sergei Morozov committed
325 326
        $schema = new Schema();
        $table  = $schema->createTable('no_connection');
327 328
        $table->addColumn('id', 'integer');

329
        $this->expectException(Exception\ReadOnlyException::class);
330 331
        $this->expectExceptionMessage(
            <<<EOT
332
An exception occurred while executing 'CREATE TABLE no_connection (id INTEGER NOT NULL)':
333

334 335 336 337 338 339 340 341 342 343 344
SQLSTATE[HY000]: General error: 8 attempt to write a readonly database
EOT
        );

        try {
            foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
                $conn->exec($sql);
            }
        } finally {
            $this->cleanupReadOnlyFile($filename);
        }
345 346
    }

347
    /**
348 349
     * @param array<string, mixed> $params
     *
350 351
     * @dataProvider getConnectionParams
     */
352
    public function testConnectionException(array $params) : void
353
    {
354 355 356
        $platform = $this->connection->getDatabasePlatform();

        if ($platform instanceof SqlitePlatform) {
Sergei Morozov's avatar
Sergei Morozov committed
357
            $this->markTestSkipped('Only skipped if platform is not sqlite');
358 359
        }

360
        if ($platform instanceof DrizzlePlatform) {
Sergei Morozov's avatar
Sergei Morozov committed
361
            $this->markTestSkipped('Drizzle does not always support authentication');
362 363
        }

364
        if ($platform instanceof PostgreSqlPlatform && isset($params['password'])) {
Sergei Morozov's avatar
Sergei Morozov committed
365
            $this->markTestSkipped('Does not work on Travis');
Benjamin Eberlei's avatar
Benjamin Eberlei committed
366 367
        }

368 369 370 371 372 373 374 375 376
        if ($platform instanceof MySqlPlatform && isset($params['user'])) {
            $wrappedConnection = $this->connection->getWrappedConnection();
            assert($wrappedConnection instanceof ServerInfoAwareConnection);

            if (version_compare($wrappedConnection->getServerVersion(), '8', '>=')) {
                $this->markTestIncomplete('PHP currently does not completely support MySQL 8');
            }
        }

Sergei Morozov's avatar
Sergei Morozov committed
377
        $defaultParams = $this->connection->getParams();
Sergei Morozov's avatar
Sergei Morozov committed
378
        $params        = array_merge($defaultParams, $params);
379

Sergei Morozov's avatar
Sergei Morozov committed
380
        $conn = DriverManager::getConnection($params);
381

Sergei Morozov's avatar
Sergei Morozov committed
382 383
        $schema = new Schema();
        $table  = $schema->createTable('no_connection');
384 385
        $table->addColumn('id', 'integer');

Luís Cobucci's avatar
Luís Cobucci committed
386
        $this->expectException(Exception\ConnectionException::class);
387

jeroendedauw's avatar
jeroendedauw committed
388
        foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
389
            $conn->exec($sql);
390
        }
391 392
    }

393 394 395 396
    /**
     * @return array<int, array<int, mixed>>
     */
    public static function getConnectionParams() : iterable
397
    {
Sergei Morozov's avatar
Sergei Morozov committed
398 399 400 401 402
        return [
            [['user' => 'not_existing']],
            [['password' => 'really_not']],
            [['host' => 'localnope']],
        ];
403
    }
404

405
    private function setUpForeignKeyConstraintViolationExceptionTest() : void
406
    {
Sergei Morozov's avatar
Sergei Morozov committed
407
        $schemaManager = $this->connection->getSchemaManager();
408

Sergei Morozov's avatar
Sergei Morozov committed
409 410 411
        $table = new Table('constraint_error_table');
        $table->addColumn('id', 'integer', []);
        $table->setPrimaryKey(['id']);
412

Sergei Morozov's avatar
Sergei Morozov committed
413 414 415 416 417
        $owningTable = new Table('owning_table');
        $owningTable->addColumn('id', 'integer', []);
        $owningTable->addColumn('constraint_id', 'integer', []);
        $owningTable->setPrimaryKey(['id']);
        $owningTable->addForeignKeyConstraint($table, ['constraint_id'], ['id']);
418 419 420 421 422

        $schemaManager->createTable($table);
        $schemaManager->createTable($owningTable);
    }

423
    private function tearDownForeignKeyConstraintViolationExceptionTest() : void
424
    {
Sergei Morozov's avatar
Sergei Morozov committed
425
        $schemaManager = $this->connection->getSchemaManager();
426 427 428 429

        $schemaManager->dropTable('owning_table');
        $schemaManager->dropTable('constraint_error_table');
    }
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444

    private function isLinuxRoot() : bool
    {
        return PHP_OS === 'Linux' && posix_getpwuid(posix_geteuid())['name'] === 'root';
    }

    private function cleanupReadOnlyFile(string $filename) : void
    {
        if ($this->isLinuxRoot()) {
            exec(sprintf('chattr -i %s', $filename));
        }

        chmod($filename, 0200); // make the file writable again, so it can be removed on Windows
        unlink($filename);
    }
445
}