Completed
Pull Request — master (#3904)
by Sergei
60:56
created

testListTableDetailsWhenCurrentSchemaNameQuoted()   A

Complexity

Conditions 1
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 6
c 1
b 1
f 0
dl 0
loc 9
rs 10
cc 1
nc 2
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Tests\DBAL\Functional\Schema;
6
7
use Doctrine\DBAL\Platforms\AbstractPlatform;
8
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
9
use Doctrine\DBAL\Schema;
10
use Doctrine\DBAL\Schema\Comparator;
11
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
12
use Doctrine\DBAL\Schema\PostgreSqlSchemaManager;
13
use Doctrine\DBAL\Schema\Table;
14
use Doctrine\DBAL\Schema\TableDiff;
15
use Doctrine\DBAL\Types\BlobType;
16
use Doctrine\DBAL\Types\DecimalType;
17
use Doctrine\DBAL\Types\Type;
18
use Doctrine\DBAL\Types\Types;
19
use function array_map;
20
use function array_pop;
21
use function count;
22
use function preg_match;
23
use function strtolower;
24
25
class PostgreSqlSchemaManagerTest extends SchemaManagerFunctionalTestCase
26
{
27
    /** @var PostgreSqlSchemaManager */
28
    protected $schemaManager;
29
30
    protected function tearDown() : void
31
    {
32
        parent::tearDown();
33
34
        $this->connection->getConfiguration()->setSchemaAssetsFilter(null);
35
    }
36
37
    /**
38
     * @group DBAL-177
39
     */
40
    public function testGetSearchPath() : void
41
    {
42
        $params = $this->connection->getParams();
43
44
        $paths = $this->schemaManager->getSchemaSearchPaths();
45
        self::assertEquals([$params['user'], 'public'], $paths);
46
    }
47
48
    /**
49
     * @group DBAL-244
50
     */
51
    public function testGetSchemaNames() : void
52
    {
53
        $names = $this->schemaManager->getSchemaNames();
54
55
        self::assertIsArray($names);
56
        self::assertNotEmpty($names);
57
        self::assertContains('public', $names, 'The public schema should be found.');
58
    }
59
60
    /**
61
     * @group DBAL-21
62
     */
63
    public function testSupportDomainTypeFallback() : void
64
    {
65
        $createDomainTypeSQL = 'CREATE DOMAIN MyMoney AS DECIMAL(18,2)';
66
        $this->connection->exec($createDomainTypeSQL);
67
68
        $createTableSQL = 'CREATE TABLE domain_type_test (id INT PRIMARY KEY, value MyMoney)';
69
        $this->connection->exec($createTableSQL);
70
71
        $table = $this->connection->getSchemaManager()->listTableDetails('domain_type_test');
72
        self::assertInstanceOf(DecimalType::class, $table->getColumn('value')->getType());
73
74
        Type::addType('MyMoney', MoneyType::class);
75
        $this->connection->getDatabasePlatform()->registerDoctrineTypeMapping('MyMoney', 'MyMoney');
76
77
        $table = $this->connection->getSchemaManager()->listTableDetails('domain_type_test');
78
        self::assertInstanceOf(MoneyType::class, $table->getColumn('value')->getType());
79
    }
80
81
    /**
82
     * @group DBAL-37
83
     */
84
    public function testDetectsAutoIncrement() : void
85
    {
86
        $autoincTable = new Table('autoinc_table');
87
        $column       = $autoincTable->addColumn('id', 'integer');
88
        $column->setAutoincrement(true);
89
        $this->schemaManager->createTable($autoincTable);
90
        $autoincTable = $this->schemaManager->listTableDetails('autoinc_table');
91
92
        self::assertTrue($autoincTable->getColumn('id')->getAutoincrement());
93
    }
94
95
    /**
96
     * @group DBAL-37
97
     */
98
    public function testAlterTableAutoIncrementAdd() : void
99
    {
100
        $tableFrom = new Table('autoinc_table_add');
101
        $tableFrom->addColumn('id', 'integer');
102
        $this->schemaManager->createTable($tableFrom);
103
        $tableFrom = $this->schemaManager->listTableDetails('autoinc_table_add');
104
        self::assertFalse($tableFrom->getColumn('id')->getAutoincrement());
105
106
        $tableTo = new Table('autoinc_table_add');
107
        $column  = $tableTo->addColumn('id', 'integer');
108
        $column->setAutoincrement(true);
109
110
        $c    = new Comparator();
111
        $diff = $c->diffTable($tableFrom, $tableTo);
112
113
        self::assertNotNull($diff);
114
115
        $sql = $this->connection->getDatabasePlatform()->getAlterTableSQL($diff);
116
        self::assertEquals([
117
            'CREATE SEQUENCE autoinc_table_add_id_seq',
118
            "SELECT setval('autoinc_table_add_id_seq', (SELECT MAX(id) FROM autoinc_table_add))",
119
            "ALTER TABLE autoinc_table_add ALTER id SET DEFAULT nextval('autoinc_table_add_id_seq')",
120
        ], $sql);
121
122
        $this->schemaManager->alterTable($diff);
123
        $tableFinal = $this->schemaManager->listTableDetails('autoinc_table_add');
124
        self::assertTrue($tableFinal->getColumn('id')->getAutoincrement());
125
    }
126
127
    /**
128
     * @group DBAL-37
129
     */
130
    public function testAlterTableAutoIncrementDrop() : void
131
    {
132
        $tableFrom = new Table('autoinc_table_drop');
133
        $column    = $tableFrom->addColumn('id', 'integer');
134
        $column->setAutoincrement(true);
135
        $this->schemaManager->createTable($tableFrom);
136
        $tableFrom = $this->schemaManager->listTableDetails('autoinc_table_drop');
137
        self::assertTrue($tableFrom->getColumn('id')->getAutoincrement());
138
139
        $tableTo = new Table('autoinc_table_drop');
140
        $tableTo->addColumn('id', 'integer');
141
142
        $c    = new Comparator();
143
        $diff = $c->diffTable($tableFrom, $tableTo);
144
145
        self::assertNotNull($diff);
146
147
        self::assertInstanceOf(TableDiff::class, $diff, 'There should be a difference and not false being returned from the table comparison');
148
        self::assertEquals(['ALTER TABLE autoinc_table_drop ALTER id DROP DEFAULT'], $this->connection->getDatabasePlatform()->getAlterTableSQL($diff));
149
150
        $this->schemaManager->alterTable($diff);
151
        $tableFinal = $this->schemaManager->listTableDetails('autoinc_table_drop');
152
        self::assertFalse($tableFinal->getColumn('id')->getAutoincrement());
153
    }
154
155
    /**
156
     * @group DBAL-75
157
     */
158
    public function testTableWithSchema() : void
159
    {
160
        $this->connection->exec('CREATE SCHEMA nested');
161
162
        $nestedRelatedTable = new Table('nested.schemarelated');
163
        $column             = $nestedRelatedTable->addColumn('id', 'integer');
164
        $column->setAutoincrement(true);
165
        $nestedRelatedTable->setPrimaryKey(['id']);
166
167
        $nestedSchemaTable = new Table('nested.schematable');
168
        $column            = $nestedSchemaTable->addColumn('id', 'integer');
169
        $column->setAutoincrement(true);
170
        $nestedSchemaTable->setPrimaryKey(['id']);
171
        $nestedSchemaTable->addForeignKeyConstraint($nestedRelatedTable, ['id'], ['id']);
172
173
        $this->schemaManager->createTable($nestedRelatedTable);
174
        $this->schemaManager->createTable($nestedSchemaTable);
175
176
        $tables = $this->schemaManager->listTableNames();
177
        self::assertContains('nested.schematable', $tables, 'The table should be detected with its non-public schema.');
178
179
        $nestedSchemaTable = $this->schemaManager->listTableDetails('nested.schematable');
180
        self::assertTrue($nestedSchemaTable->hasColumn('id'));
181
182
        $primaryKey = $nestedSchemaTable->getPrimaryKey();
183
        self::assertNotNull($primaryKey);
184
        self::assertEquals(['id'], $primaryKey->getColumns());
185
186
        $relatedFks = $nestedSchemaTable->getForeignKeys();
187
        self::assertCount(1, $relatedFks);
188
        $relatedFk = array_pop($relatedFks);
189
        self::assertNotNull($relatedFk);
190
        self::assertEquals('nested.schemarelated', $relatedFk->getForeignTableName());
191
    }
192
193
    /**
194
     * @group DBAL-91
195
     * @group DBAL-88
196
     */
197
    public function testReturnQuotedAssets() : void
198
    {
199
        $sql = 'create table dbal91_something ( id integer  CONSTRAINT id_something PRIMARY KEY NOT NULL  ,"table"   integer );';
200
        $this->connection->exec($sql);
201
202
        $sql = 'ALTER TABLE dbal91_something ADD CONSTRAINT something_input FOREIGN KEY( "table" ) REFERENCES dbal91_something ON UPDATE CASCADE;';
203
        $this->connection->exec($sql);
204
205
        $table = $this->schemaManager->listTableDetails('dbal91_something');
206
207
        self::assertEquals(
208
            [
209
                'CREATE TABLE dbal91_something (id INT NOT NULL, "table" INT DEFAULT NULL, PRIMARY KEY(id))',
210
                'CREATE INDEX IDX_A9401304ECA7352B ON dbal91_something ("table")',
211
            ],
212
            $this->connection->getDatabasePlatform()->getCreateTableSQL($table)
213
        );
214
    }
215
216
    /**
217
     * @group DBAL-204
218
     */
219
    public function testFilterSchemaExpression() : void
220
    {
221
        $testTable = new Table('dbal204_test_prefix');
222
        $testTable->addColumn('id', 'integer');
223
        $this->schemaManager->createTable($testTable);
224
        $testTable = new Table('dbal204_without_prefix');
225
        $testTable->addColumn('id', 'integer');
226
        $this->schemaManager->createTable($testTable);
227
228
        $this->connection->getConfiguration()->setSchemaAssetsFilter(static function (string $name) : bool {
229
            return preg_match('#^dbal204_#', $name) === 1;
230
        });
231
        $names = $this->schemaManager->listTableNames();
232
        self::assertCount(2, $names);
233
234
        $this->connection->getConfiguration()->setSchemaAssetsFilter(static function (string $name) : bool {
235
            return preg_match('#^dbal204_test#', $name) === 1;
236
        });
237
        $names = $this->schemaManager->listTableNames();
238
        self::assertCount(1, $names);
239
    }
240
241
    public function testListForeignKeys() : void
242
    {
243
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
244
            $this->markTestSkipped('Does not support foreign key constraints.');
245
        }
246
247
        $fkOptions   = ['SET NULL', 'SET DEFAULT', 'NO ACTION','CASCADE', 'RESTRICT'];
248
        $foreignKeys = [];
249
        $fkTable     = $this->getTestTable('test_create_fk1');
250
        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...
251
            $fkTable->addColumn('foreign_key_test' . $i, 'integer');
252
            $foreignKeys[] = new ForeignKeyConstraint(
253
                ['foreign_key_test' . $i],
254
                'test_create_fk2',
255
                ['id'],
256
                'foreign_key_test' . $i . '_fk',
257
                ['onDelete' => $fkOptions[$i]]
258
            );
259
        }
260
261
        $this->schemaManager->dropAndCreateTable($fkTable);
262
        $this->createTestTable('test_create_fk2');
263
264
        foreach ($foreignKeys as $foreignKey) {
265
            $this->schemaManager->createForeignKey($foreignKey, 'test_create_fk1');
266
        }
267
268
        $fkeys = $this->schemaManager->listTableForeignKeys('test_create_fk1');
269
        self::assertEquals(count($foreignKeys), count($fkeys), "Table 'test_create_fk1' has to have " . count($foreignKeys) . ' foreign keys.');
270
        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...
271
            self::assertEquals(['foreign_key_test' . $i], array_map('strtolower', $fkeys[$i]->getLocalColumns()));
272
            self::assertEquals(['id'], array_map('strtolower', $fkeys[$i]->getForeignColumns()));
273
            self::assertEquals('test_create_fk2', strtolower($fkeys[0]->getForeignTableName()));
274
            if ($foreignKeys[$i]->getOption('onDelete') === 'NO ACTION') {
275
                self::assertFalse($fkeys[$i]->hasOption('onDelete'), 'Unexpected option: ' . $fkeys[$i]->getOption('onDelete'));
276
            } else {
277
                self::assertEquals($foreignKeys[$i]->getOption('onDelete'), $fkeys[$i]->getOption('onDelete'));
278
            }
279
        }
280
    }
281
282
    /**
283
     * @group DBAL-511
284
     */
285
    public function testDefaultValueCharacterVarying() : void
286
    {
287
        $testTable = new Table('dbal511_default');
288
        $testTable->addColumn('id', 'integer');
289
        $testTable->addColumn('def', 'string', ['default' => 'foo']);
290
        $testTable->setPrimaryKey(['id']);
291
292
        $this->schemaManager->createTable($testTable);
293
294
        $databaseTable = $this->schemaManager->listTableDetails($testTable->getName());
295
296
        self::assertEquals('foo', $databaseTable->getColumn('def')->getDefault());
297
    }
298
299
    /**
300
     * @group DDC-2843
301
     */
302
    public function testBooleanDefault() : void
303
    {
304
        $table = new Table('ddc2843_bools');
305
        $table->addColumn('id', 'integer');
306
        $table->addColumn('checked', 'boolean', ['default' => false]);
307
308
        $this->schemaManager->createTable($table);
309
310
        $databaseTable = $this->schemaManager->listTableDetails($table->getName());
311
312
        $c    = new Comparator();
313
        $diff = $c->diffTable($table, $databaseTable);
314
315
        self::assertNull($diff);
316
    }
317
318
    public function testListTableWithBinary() : void
319
    {
320
        $tableName = 'test_binary_table';
321
322
        $table = new Table($tableName);
323
        $table->addColumn('id', 'integer');
324
        $table->addColumn('column_varbinary', 'binary', []);
325
        $table->addColumn('column_binary', 'binary', ['fixed' => true]);
326
        $table->setPrimaryKey(['id']);
327
328
        $this->schemaManager->createTable($table);
329
330
        $table = $this->schemaManager->listTableDetails($tableName);
331
332
        self::assertInstanceOf(BlobType::class, $table->getColumn('column_varbinary')->getType());
333
        self::assertFalse($table->getColumn('column_varbinary')->getFixed());
334
335
        self::assertInstanceOf(BlobType::class, $table->getColumn('column_binary')->getType());
336
        self::assertFalse($table->getColumn('column_binary')->getFixed());
337
    }
338
339
    public function testListQuotedTable() : void
340
    {
341
        $offlineTable = new Schema\Table('user');
342
        $offlineTable->addColumn('id', 'integer');
343
        $offlineTable->addColumn('username', 'string');
344
        $offlineTable->addColumn('fk', 'integer');
345
        $offlineTable->setPrimaryKey(['id']);
346
        $offlineTable->addForeignKeyConstraint($offlineTable, ['fk'], ['id']);
347
348
        $this->schemaManager->dropAndCreateTable($offlineTable);
349
350
        $onlineTable = $this->schemaManager->listTableDetails('"user"');
351
352
        $comparator = new Schema\Comparator();
353
354
        self::assertNull($comparator->diffTable($offlineTable, $onlineTable));
355
    }
356
357
    public function testListTableDetailsWhenCurrentSchemaNameQuoted() : void
358
    {
359
        $this->connection->exec('CREATE SCHEMA "001_test"');
360
        $this->connection->exec('SET search_path TO "001_test"');
361
362
        try {
363
            $this->testListQuotedTable();
364
        } finally {
365
            $this->connection->close();
366
        }
367
    }
368
369
    public function testListTablesExcludesViews() : void
370
    {
371
        $this->createTestTable('list_tables_excludes_views');
372
373
        $name = 'list_tables_excludes_views_test_view';
374
        $sql  = 'SELECT * from list_tables_excludes_views';
375
376
        $view = new Schema\View($name, $sql);
377
378
        $this->schemaManager->dropAndCreateView($view);
379
380
        $tables = $this->schemaManager->listTables();
381
382
        $foundTable = false;
383
        foreach ($tables as $table) {
384
            self::assertInstanceOf(Table::class, $table, 'No Table instance was found in tables array.');
385
            if (strtolower($table->getName()) !== 'list_tables_excludes_views_test_view') {
386
                continue;
387
            }
388
389
            $foundTable = true;
390
        }
391
392
        self::assertFalse($foundTable, 'View "list_tables_excludes_views_test_view" must not be found in table list');
393
    }
394
395
    /**
396
     * @group DBAL-1033
397
     */
398
    public function testPartialIndexes() : void
399
    {
400
        $offlineTable = new Schema\Table('person');
401
        $offlineTable->addColumn('id', 'integer');
402
        $offlineTable->addColumn('name', 'string');
403
        $offlineTable->addColumn('email', 'string');
404
        $offlineTable->addUniqueIndex(['id', 'name'], 'simple_partial_index', ['where' => '(id IS NULL)']);
405
406
        $this->schemaManager->dropAndCreateTable($offlineTable);
407
408
        $onlineTable = $this->schemaManager->listTableDetails('person');
409
410
        $comparator = new Schema\Comparator();
411
412
        self::assertNull($comparator->diffTable($offlineTable, $onlineTable));
413
        self::assertTrue($onlineTable->hasIndex('simple_partial_index'));
414
        self::assertTrue($onlineTable->getIndex('simple_partial_index')->hasOption('where'));
415
        self::assertSame('(id IS NULL)', $onlineTable->getIndex('simple_partial_index')->getOption('where'));
416
    }
417
418
    public function testJsonbColumn() : void
419
    {
420
        if (! $this->schemaManager->getDatabasePlatform() instanceof PostgreSqlPlatform) {
421
            $this->markTestSkipped('Requires PostgresSQL 9.4+');
422
423
            return;
424
        }
425
426
        $table = new Schema\Table('test_jsonb');
427
        $table->addColumn('foo', Types::JSON)->setPlatformOption('jsonb', true);
428
        $this->schemaManager->dropAndCreateTable($table);
429
430
        $columns = $this->schemaManager->listTableColumns('test_jsonb');
431
432
        self::assertSame(Types::JSON, $columns['foo']->getType()->getName());
433
        self::assertTrue($columns['foo']->getPlatformOption('jsonb'));
434
    }
435
436
    /**
437
     * @group DBAL-2427
438
     */
439
    public function testListNegativeColumnDefaultValue() : void
440
    {
441
        $table = new Schema\Table('test_default_negative');
442
        $table->addColumn('col_smallint', 'smallint', ['default' => -1]);
443
        $table->addColumn('col_integer', 'integer', ['default' => -1]);
444
        $table->addColumn('col_bigint', 'bigint', ['default' => -1]);
445
        $table->addColumn('col_float', 'float', ['default' => -1.1]);
446
        $table->addColumn('col_decimal', 'decimal', ['default' => -1.1]);
447
        $table->addColumn('col_string', 'string', ['default' => '(-1)']);
448
449
        $this->schemaManager->dropAndCreateTable($table);
450
451
        $columns = $this->schemaManager->listTableColumns('test_default_negative');
452
453
        self::assertEquals(-1, $columns['col_smallint']->getDefault());
454
        self::assertEquals(-1, $columns['col_integer']->getDefault());
455
        self::assertEquals(-1, $columns['col_bigint']->getDefault());
456
        self::assertEquals(-1.1, $columns['col_float']->getDefault());
457
        self::assertEquals(-1.1, $columns['col_decimal']->getDefault());
458
        self::assertEquals('(-1)', $columns['col_string']->getDefault());
459
    }
460
461
    /**
462
     * @return mixed[][]
463
     */
464
    public static function serialTypes() : iterable
465
    {
466
        return [
467
            ['integer'],
468
            ['bigint'],
469
        ];
470
    }
471
472
    /**
473
     * @dataProvider serialTypes
474
     * @group 2906
475
     */
476
    public function testAutoIncrementCreatesSerialDataTypesWithoutADefaultValue(string $type) : void
477
    {
478
        $tableName = 'test_serial_type_' . $type;
479
480
        $table = new Schema\Table($tableName);
481
        $table->addColumn('id', $type, ['autoincrement' => true, 'notnull' => false]);
482
483
        $this->schemaManager->dropAndCreateTable($table);
484
485
        $columns = $this->schemaManager->listTableColumns($tableName);
486
487
        self::assertNull($columns['id']->getDefault());
488
    }
489
490
    /**
491
     * @dataProvider serialTypes
492
     * @group 2906
493
     */
494
    public function testAutoIncrementCreatesSerialDataTypesWithoutADefaultValueEvenWhenDefaultIsSet(string $type) : void
495
    {
496
        $tableName = 'test_serial_type_with_default_' . $type;
497
498
        $table = new Schema\Table($tableName);
499
        $table->addColumn('id', $type, ['autoincrement' => true, 'notnull' => false, 'default' => 1]);
500
501
        $this->schemaManager->dropAndCreateTable($table);
502
503
        $columns = $this->schemaManager->listTableColumns($tableName);
504
505
        self::assertNull($columns['id']->getDefault());
506
    }
507
508
    /**
509
     * @group 2916
510
     * @dataProvider autoIncrementTypeMigrations
511
     */
512
    public function testAlterTableAutoIncrementIntToBigInt(string $from, string $to, string $expected) : void
513
    {
514
        $tableFrom = new Table('autoinc_type_modification');
515
        $column    = $tableFrom->addColumn('id', $from);
516
        $column->setAutoincrement(true);
517
        $this->schemaManager->dropAndCreateTable($tableFrom);
518
        $tableFrom = $this->schemaManager->listTableDetails('autoinc_type_modification');
519
        self::assertTrue($tableFrom->getColumn('id')->getAutoincrement());
520
521
        $tableTo = new Table('autoinc_type_modification');
522
        $column  = $tableTo->addColumn('id', $to);
523
        $column->setAutoincrement(true);
524
525
        $c    = new Comparator();
526
        $diff = $c->diffTable($tableFrom, $tableTo);
527
        self::assertInstanceOf(TableDiff::class, $diff, 'There should be a difference and not false being returned from the table comparison');
528
        self::assertSame(['ALTER TABLE autoinc_type_modification ALTER id TYPE ' . $expected], $this->connection->getDatabasePlatform()->getAlterTableSQL($diff));
529
530
        $this->schemaManager->alterTable($diff);
531
        $tableFinal = $this->schemaManager->listTableDetails('autoinc_type_modification');
532
        self::assertTrue($tableFinal->getColumn('id')->getAutoincrement());
533
    }
534
535
    /**
536
     * @return mixed[][]
537
     */
538
    public static function autoIncrementTypeMigrations() : iterable
539
    {
540
        return [
541
            'int->bigint' => ['integer', 'bigint', 'BIGINT'],
542
            'bigint->int' => ['bigint', 'integer', 'INT'],
543
        ];
544
    }
545
}
546
547
class MoneyType extends Type
548
{
549
    public function getName() : string
550
    {
551
        return 'MyMoney';
552
    }
553
554
    /**
555
     * {@inheritDoc}
556
     */
557
    public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) : string
558
    {
559
        return 'MyMoney';
560
    }
561
}
562