Issues (201)

Functional/Schema/PostgreSqlSchemaManagerTest.php (2 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Tests\Functional\Schema;
6
7
use Doctrine\DBAL\Platforms\AbstractPlatform;
8
use Doctrine\DBAL\Schema;
9
use Doctrine\DBAL\Schema\Comparator;
10
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
11
use Doctrine\DBAL\Schema\PostgreSqlSchemaManager;
12
use Doctrine\DBAL\Schema\Table;
13
use Doctrine\DBAL\Schema\TableDiff;
14
use Doctrine\DBAL\Types\BlobType;
15
use Doctrine\DBAL\Types\DecimalType;
16
use Doctrine\DBAL\Types\Type;
17
use Doctrine\DBAL\Types\Types;
18
use function array_map;
19
use function array_pop;
20
use function count;
21
use function preg_match;
22
use function strtolower;
23
24
class PostgreSqlSchemaManagerTest extends SchemaManagerFunctionalTestCase
25
{
26
    /** @var PostgreSqlSchemaManager */
27
    protected $schemaManager;
28
29
    protected function tearDown() : void
30
    {
31
        parent::tearDown();
32
33
        $this->connection->getConfiguration()->setSchemaAssetsFilter(null);
34
    }
35
36
    /**
37
     * @group DBAL-177
38
     */
39
    public function testGetSearchPath() : void
40
    {
41
        $params = $this->connection->getParams();
42
43
        $paths = $this->schemaManager->getSchemaSearchPaths();
44
        self::assertEquals([$params['user'], 'public'], $paths);
45
    }
46
47
    /**
48
     * @group DBAL-244
49
     */
50
    public function testGetSchemaNames() : void
51
    {
52
        $names = $this->schemaManager->getSchemaNames();
53
54
        self::assertNotEmpty($names);
55
        self::assertContains('public', $names, 'The public schema should be found.');
56
    }
57
58
    /**
59
     * @group DBAL-21
60
     */
61
    public function testSupportDomainTypeFallback() : void
62
    {
63
        $createDomainTypeSQL = 'CREATE DOMAIN MyMoney AS DECIMAL(18,2)';
64
        $this->connection->exec($createDomainTypeSQL);
65
66
        $createTableSQL = 'CREATE TABLE domain_type_test (id INT PRIMARY KEY, value MyMoney)';
67
        $this->connection->exec($createTableSQL);
68
69
        $table = $this->connection->getSchemaManager()->listTableDetails('domain_type_test');
70
        self::assertInstanceOf(DecimalType::class, $table->getColumn('value')->getType());
71
72
        Type::addType('MyMoney', MoneyType::class);
73
        $this->connection->getDatabasePlatform()->registerDoctrineTypeMapping('MyMoney', 'MyMoney');
74
75
        $table = $this->connection->getSchemaManager()->listTableDetails('domain_type_test');
76
        self::assertInstanceOf(MoneyType::class, $table->getColumn('value')->getType());
77
    }
78
79
    /**
80
     * @group DBAL-37
81
     */
82
    public function testDetectsAutoIncrement() : void
83
    {
84
        $autoincTable = new Table('autoinc_table');
85
        $column       = $autoincTable->addColumn('id', 'integer');
86
        $column->setAutoincrement(true);
87
        $this->schemaManager->createTable($autoincTable);
88
        $autoincTable = $this->schemaManager->listTableDetails('autoinc_table');
89
90
        self::assertTrue($autoincTable->getColumn('id')->getAutoincrement());
91
    }
92
93
    /**
94
     * @group DBAL-37
95
     */
96
    public function testAlterTableAutoIncrementAdd() : void
97
    {
98
        $tableFrom = new Table('autoinc_table_add');
99
        $tableFrom->addColumn('id', 'integer');
100
        $this->schemaManager->createTable($tableFrom);
101
        $tableFrom = $this->schemaManager->listTableDetails('autoinc_table_add');
102
        self::assertFalse($tableFrom->getColumn('id')->getAutoincrement());
103
104
        $tableTo = new Table('autoinc_table_add');
105
        $column  = $tableTo->addColumn('id', 'integer');
106
        $column->setAutoincrement(true);
107
108
        $c    = new Comparator();
109
        $diff = $c->diffTable($tableFrom, $tableTo);
110
111
        self::assertNotNull($diff);
112
113
        $sql = $this->connection->getDatabasePlatform()->getAlterTableSQL($diff);
114
        self::assertEquals([
115
            'CREATE SEQUENCE autoinc_table_add_id_seq',
116
            "SELECT setval('autoinc_table_add_id_seq', (SELECT MAX(id) FROM autoinc_table_add))",
117
            "ALTER TABLE autoinc_table_add ALTER id SET DEFAULT nextval('autoinc_table_add_id_seq')",
118
        ], $sql);
119
120
        $this->schemaManager->alterTable($diff);
121
        $tableFinal = $this->schemaManager->listTableDetails('autoinc_table_add');
122
        self::assertTrue($tableFinal->getColumn('id')->getAutoincrement());
123
    }
124
125
    /**
126
     * @group DBAL-37
127
     */
128
    public function testAlterTableAutoIncrementDrop() : void
129
    {
130
        $tableFrom = new Table('autoinc_table_drop');
131
        $column    = $tableFrom->addColumn('id', 'integer');
132
        $column->setAutoincrement(true);
133
        $this->schemaManager->createTable($tableFrom);
134
        $tableFrom = $this->schemaManager->listTableDetails('autoinc_table_drop');
135
        self::assertTrue($tableFrom->getColumn('id')->getAutoincrement());
136
137
        $tableTo = new Table('autoinc_table_drop');
138
        $tableTo->addColumn('id', 'integer');
139
140
        $c    = new Comparator();
141
        $diff = $c->diffTable($tableFrom, $tableTo);
142
143
        self::assertNotNull($diff);
144
145
        self::assertEquals(['ALTER TABLE autoinc_table_drop ALTER id DROP DEFAULT'], $this->connection->getDatabasePlatform()->getAlterTableSQL($diff));
146
147
        $this->schemaManager->alterTable($diff);
148
        $tableFinal = $this->schemaManager->listTableDetails('autoinc_table_drop');
149
        self::assertFalse($tableFinal->getColumn('id')->getAutoincrement());
150
    }
151
152
    /**
153
     * @group DBAL-75
154
     */
155
    public function testTableWithSchema() : void
156
    {
157
        $this->connection->exec('CREATE SCHEMA nested');
158
159
        $nestedRelatedTable = new Table('nested.schemarelated');
160
        $column             = $nestedRelatedTable->addColumn('id', 'integer');
161
        $column->setAutoincrement(true);
162
        $nestedRelatedTable->setPrimaryKey(['id']);
163
164
        $nestedSchemaTable = new Table('nested.schematable');
165
        $column            = $nestedSchemaTable->addColumn('id', 'integer');
166
        $column->setAutoincrement(true);
167
        $nestedSchemaTable->setPrimaryKey(['id']);
168
        $nestedSchemaTable->addForeignKeyConstraint($nestedRelatedTable, ['id'], ['id']);
169
170
        $this->schemaManager->createTable($nestedRelatedTable);
171
        $this->schemaManager->createTable($nestedSchemaTable);
172
173
        $tables = $this->schemaManager->listTableNames();
174
        self::assertContains('nested.schematable', $tables, 'The table should be detected with its non-public schema.');
175
176
        $nestedSchemaTable = $this->schemaManager->listTableDetails('nested.schematable');
177
        self::assertTrue($nestedSchemaTable->hasColumn('id'));
178
179
        $primaryKey = $nestedSchemaTable->getPrimaryKey();
180
        self::assertNotNull($primaryKey);
181
        self::assertEquals(['id'], $primaryKey->getColumns());
182
183
        $relatedFks = $nestedSchemaTable->getForeignKeys();
184
        self::assertCount(1, $relatedFks);
185
        $relatedFk = array_pop($relatedFks);
186
        self::assertNotNull($relatedFk);
187
        self::assertEquals('nested.schemarelated', $relatedFk->getForeignTableName());
188
    }
189
190
    /**
191
     * @group DBAL-91
192
     * @group DBAL-88
193
     */
194
    public function testReturnQuotedAssets() : void
195
    {
196
        $sql = 'create table dbal91_something ( id integer  CONSTRAINT id_something PRIMARY KEY NOT NULL  ,"table"   integer );';
197
        $this->connection->exec($sql);
198
199
        $sql = 'ALTER TABLE dbal91_something ADD CONSTRAINT something_input FOREIGN KEY( "table" ) REFERENCES dbal91_something ON UPDATE CASCADE;';
200
        $this->connection->exec($sql);
201
202
        $table = $this->schemaManager->listTableDetails('dbal91_something');
203
204
        self::assertEquals(
205
            [
206
                'CREATE TABLE dbal91_something (id INT NOT NULL, "table" INT DEFAULT NULL, PRIMARY KEY(id))',
207
                'CREATE INDEX IDX_A9401304ECA7352B ON dbal91_something ("table")',
208
            ],
209
            $this->connection->getDatabasePlatform()->getCreateTableSQL($table)
210
        );
211
    }
212
213
    /**
214
     * @group DBAL-204
215
     */
216
    public function testFilterSchemaExpression() : void
217
    {
218
        $testTable = new Table('dbal204_test_prefix');
219
        $testTable->addColumn('id', 'integer');
220
        $this->schemaManager->createTable($testTable);
221
        $testTable = new Table('dbal204_without_prefix');
222
        $testTable->addColumn('id', 'integer');
223
        $this->schemaManager->createTable($testTable);
224
225
        $this->connection->getConfiguration()->setSchemaAssetsFilter(static function (string $name) : bool {
226
            return preg_match('#^dbal204_#', $name) === 1;
227
        });
228
        $names = $this->schemaManager->listTableNames();
229
        self::assertCount(2, $names);
230
231
        $this->connection->getConfiguration()->setSchemaAssetsFilter(static function (string $name) : bool {
232
            return preg_match('#^dbal204_test#', $name) === 1;
233
        });
234
        $names = $this->schemaManager->listTableNames();
235
        self::assertCount(1, $names);
236
    }
237
238
    public function testListForeignKeys() : void
239
    {
240
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
241
            self::markTestSkipped('Does not support foreign key constraints.');
242
        }
243
244
        $fkOptions   = ['SET NULL', 'SET DEFAULT', 'NO ACTION','CASCADE', 'RESTRICT'];
245
        $foreignKeys = [];
246
        $fkTable     = $this->getTestTable('test_create_fk1');
247
        for ($i = 0; $i < count($fkOptions); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
248
            $fkTable->addColumn('foreign_key_test' . $i, 'integer');
249
            $foreignKeys[] = new ForeignKeyConstraint(
250
                ['foreign_key_test' . $i],
251
                'test_create_fk2',
252
                ['id'],
253
                'foreign_key_test' . $i . '_fk',
254
                ['onDelete' => $fkOptions[$i]]
255
            );
256
        }
257
258
        $this->schemaManager->dropAndCreateTable($fkTable);
259
        $this->createTestTable('test_create_fk2');
260
261
        foreach ($foreignKeys as $foreignKey) {
262
            $this->schemaManager->createForeignKey($foreignKey, 'test_create_fk1');
263
        }
264
265
        $fkeys = $this->schemaManager->listTableForeignKeys('test_create_fk1');
266
        self::assertEquals(count($foreignKeys), count($fkeys), "Table 'test_create_fk1' has to have " . count($foreignKeys) . ' foreign keys.');
267
        for ($i = 0; $i < count($fkeys); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
268
            self::assertEquals(['foreign_key_test' . $i], array_map('strtolower', $fkeys[$i]->getLocalColumns()));
269
            self::assertEquals(['id'], array_map('strtolower', $fkeys[$i]->getForeignColumns()));
270
            self::assertEquals('test_create_fk2', strtolower($fkeys[0]->getForeignTableName()));
271
            if ($foreignKeys[$i]->getOption('onDelete') === 'NO ACTION') {
272
                self::assertFalse($fkeys[$i]->hasOption('onDelete'), 'Unexpected option: ' . $fkeys[$i]->getOption('onDelete'));
273
            } else {
274
                self::assertEquals($foreignKeys[$i]->getOption('onDelete'), $fkeys[$i]->getOption('onDelete'));
275
            }
276
        }
277
    }
278
279
    /**
280
     * @group DBAL-511
281
     */
282
    public function testDefaultValueCharacterVarying() : void
283
    {
284
        $testTable = new Table('dbal511_default');
285
        $testTable->addColumn('id', 'integer');
286
        $testTable->addColumn('def', 'string', ['default' => 'foo']);
287
        $testTable->setPrimaryKey(['id']);
288
289
        $this->schemaManager->createTable($testTable);
290
291
        $databaseTable = $this->schemaManager->listTableDetails($testTable->getName());
292
293
        self::assertEquals('foo', $databaseTable->getColumn('def')->getDefault());
294
    }
295
296
    /**
297
     * @group DDC-2843
298
     */
299
    public function testBooleanDefault() : void
300
    {
301
        $table = new Table('ddc2843_bools');
302
        $table->addColumn('id', 'integer');
303
        $table->addColumn('checked', 'boolean', ['default' => false]);
304
305
        $this->schemaManager->createTable($table);
306
307
        $databaseTable = $this->schemaManager->listTableDetails($table->getName());
308
309
        $c    = new Comparator();
310
        $diff = $c->diffTable($table, $databaseTable);
311
312
        self::assertNull($diff);
313
    }
314
315
    public function testListTableWithBinary() : void
316
    {
317
        $tableName = 'test_binary_table';
318
319
        $table = new Table($tableName);
320
        $table->addColumn('id', 'integer');
321
        $table->addColumn('column_varbinary', 'binary', []);
322
        $table->addColumn('column_binary', 'binary', ['fixed' => true]);
323
        $table->setPrimaryKey(['id']);
324
325
        $this->schemaManager->createTable($table);
326
327
        $table = $this->schemaManager->listTableDetails($tableName);
328
329
        self::assertInstanceOf(BlobType::class, $table->getColumn('column_varbinary')->getType());
330
        self::assertFalse($table->getColumn('column_varbinary')->getFixed());
331
332
        self::assertInstanceOf(BlobType::class, $table->getColumn('column_binary')->getType());
333
        self::assertFalse($table->getColumn('column_binary')->getFixed());
334
    }
335
336
    public function testListQuotedTable() : void
337
    {
338
        $offlineTable = new Schema\Table('user');
339
        $offlineTable->addColumn('id', 'integer');
340
        $offlineTable->addColumn('username', 'string');
341
        $offlineTable->addColumn('fk', 'integer');
342
        $offlineTable->setPrimaryKey(['id']);
343
        $offlineTable->addForeignKeyConstraint($offlineTable, ['fk'], ['id']);
344
345
        $this->schemaManager->dropAndCreateTable($offlineTable);
346
347
        $onlineTable = $this->schemaManager->listTableDetails('"user"');
348
349
        $comparator = new Schema\Comparator();
350
351
        self::assertNull($comparator->diffTable($offlineTable, $onlineTable));
352
    }
353
354
    public function testListTableDetailsWhenCurrentSchemaNameQuoted() : void
355
    {
356
        $this->connection->exec('CREATE SCHEMA "001_test"');
357
        $this->connection->exec('SET search_path TO "001_test"');
358
359
        try {
360
            $this->testListQuotedTable();
361
        } finally {
362
            $this->connection->close();
363
        }
364
    }
365
366
    public function testListTablesExcludesViews() : void
367
    {
368
        $this->createTestTable('list_tables_excludes_views');
369
370
        $name = 'list_tables_excludes_views_test_view';
371
        $sql  = 'SELECT * from list_tables_excludes_views';
372
373
        $view = new Schema\View($name, $sql);
374
375
        $this->schemaManager->dropAndCreateView($view);
376
377
        $tables = $this->schemaManager->listTables();
378
379
        $foundTable = false;
380
        foreach ($tables as $table) {
381
            if (strtolower($table->getName()) !== 'list_tables_excludes_views_test_view') {
382
                continue;
383
            }
384
385
            $foundTable = true;
386
        }
387
388
        self::assertFalse($foundTable, 'View "list_tables_excludes_views_test_view" must not be found in table list');
389
    }
390
391
    /**
392
     * @group DBAL-1033
393
     */
394
    public function testPartialIndexes() : void
395
    {
396
        $offlineTable = new Schema\Table('person');
397
        $offlineTable->addColumn('id', 'integer');
398
        $offlineTable->addColumn('name', 'string');
399
        $offlineTable->addColumn('email', 'string');
400
        $offlineTable->addUniqueIndex(['id', 'name'], 'simple_partial_index', ['where' => '(id IS NULL)']);
401
402
        $this->schemaManager->dropAndCreateTable($offlineTable);
403
404
        $onlineTable = $this->schemaManager->listTableDetails('person');
405
406
        $comparator = new Schema\Comparator();
407
408
        self::assertNull($comparator->diffTable($offlineTable, $onlineTable));
409
        self::assertTrue($onlineTable->hasIndex('simple_partial_index'));
410
        self::assertTrue($onlineTable->getIndex('simple_partial_index')->hasOption('where'));
411
        self::assertSame('(id IS NULL)', $onlineTable->getIndex('simple_partial_index')->getOption('where'));
412
    }
413
414
    public function testJsonbColumn() : void
415
    {
416
        $table = new Schema\Table('test_jsonb');
417
        $table->addColumn('foo', Types::JSON)->setPlatformOption('jsonb', true);
418
        $this->schemaManager->dropAndCreateTable($table);
419
420
        $columns = $this->schemaManager->listTableColumns('test_jsonb');
421
422
        self::assertSame(Types::JSON, $columns['foo']->getType()->getName());
423
        self::assertTrue($columns['foo']->getPlatformOption('jsonb'));
424
    }
425
426
    /**
427
     * @group DBAL-2427
428
     */
429
    public function testListNegativeColumnDefaultValue() : void
430
    {
431
        $table = new Schema\Table('test_default_negative');
432
        $table->addColumn('col_smallint', 'smallint', ['default' => -1]);
433
        $table->addColumn('col_integer', 'integer', ['default' => -1]);
434
        $table->addColumn('col_bigint', 'bigint', ['default' => -1]);
435
        $table->addColumn('col_float', 'float', ['default' => -1.1]);
436
        $table->addColumn('col_decimal', 'decimal', ['default' => -1.1]);
437
        $table->addColumn('col_string', 'string', ['default' => '(-1)']);
438
439
        $this->schemaManager->dropAndCreateTable($table);
440
441
        $columns = $this->schemaManager->listTableColumns('test_default_negative');
442
443
        self::assertEquals(-1, $columns['col_smallint']->getDefault());
444
        self::assertEquals(-1, $columns['col_integer']->getDefault());
445
        self::assertEquals(-1, $columns['col_bigint']->getDefault());
446
        self::assertEquals(-1.1, $columns['col_float']->getDefault());
447
        self::assertEquals(-1.1, $columns['col_decimal']->getDefault());
448
        self::assertEquals('(-1)', $columns['col_string']->getDefault());
449
    }
450
451
    /**
452
     * @return mixed[][]
453
     */
454
    public static function serialTypes() : iterable
455
    {
456
        return [
457
            ['integer'],
458
            ['bigint'],
459
        ];
460
    }
461
462
    /**
463
     * @dataProvider serialTypes
464
     * @group 2906
465
     */
466
    public function testAutoIncrementCreatesSerialDataTypesWithoutADefaultValue(string $type) : void
467
    {
468
        $tableName = 'test_serial_type_' . $type;
469
470
        $table = new Schema\Table($tableName);
471
        $table->addColumn('id', $type, ['autoincrement' => true, 'notnull' => false]);
472
473
        $this->schemaManager->dropAndCreateTable($table);
474
475
        $columns = $this->schemaManager->listTableColumns($tableName);
476
477
        self::assertNull($columns['id']->getDefault());
478
    }
479
480
    /**
481
     * @dataProvider serialTypes
482
     * @group 2906
483
     */
484
    public function testAutoIncrementCreatesSerialDataTypesWithoutADefaultValueEvenWhenDefaultIsSet(string $type) : void
485
    {
486
        $tableName = 'test_serial_type_with_default_' . $type;
487
488
        $table = new Schema\Table($tableName);
489
        $table->addColumn('id', $type, ['autoincrement' => true, 'notnull' => false, 'default' => 1]);
490
491
        $this->schemaManager->dropAndCreateTable($table);
492
493
        $columns = $this->schemaManager->listTableColumns($tableName);
494
495
        self::assertNull($columns['id']->getDefault());
496
    }
497
498
    /**
499
     * @group 2916
500
     * @dataProvider autoIncrementTypeMigrations
501
     */
502
    public function testAlterTableAutoIncrementIntToBigInt(string $from, string $to, string $expected) : void
503
    {
504
        $tableFrom = new Table('autoinc_type_modification');
505
        $column    = $tableFrom->addColumn('id', $from);
506
        $column->setAutoincrement(true);
507
        $this->schemaManager->dropAndCreateTable($tableFrom);
508
        $tableFrom = $this->schemaManager->listTableDetails('autoinc_type_modification');
509
        self::assertTrue($tableFrom->getColumn('id')->getAutoincrement());
510
511
        $tableTo = new Table('autoinc_type_modification');
512
        $column  = $tableTo->addColumn('id', $to);
513
        $column->setAutoincrement(true);
514
515
        $c    = new Comparator();
516
        $diff = $c->diffTable($tableFrom, $tableTo);
517
        self::assertInstanceOf(TableDiff::class, $diff, 'There should be a difference and not false being returned from the table comparison');
518
        self::assertSame(['ALTER TABLE autoinc_type_modification ALTER id TYPE ' . $expected], $this->connection->getDatabasePlatform()->getAlterTableSQL($diff));
519
520
        $this->schemaManager->alterTable($diff);
521
        $tableFinal = $this->schemaManager->listTableDetails('autoinc_type_modification');
522
        self::assertTrue($tableFinal->getColumn('id')->getAutoincrement());
523
    }
524
525
    /**
526
     * @return mixed[][]
527
     */
528
    public static function autoIncrementTypeMigrations() : iterable
529
    {
530
        return [
531
            'int->bigint' => ['integer', 'bigint', 'BIGINT'],
532
            'bigint->int' => ['bigint', 'integer', 'INT'],
533
        ];
534
    }
535
}
536
537
class MoneyType extends Type
538
{
539
    public function getName() : string
540
    {
541
        return 'MyMoney';
542
    }
543
544
    /**
545
     * {@inheritDoc}
546
     */
547
    public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) : string
548
    {
549
        return 'MyMoney';
550
    }
551
}
552