Completed
Pull Request — master (#3769)
by Benjamin
65:11 queued 11s
created

testUsesSequenceEmulatedIdentityColumns()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Tests\DBAL\Platforms;
6
7
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
8
use Doctrine\DBAL\Schema\Column;
9
use Doctrine\DBAL\Schema\ColumnDiff;
10
use Doctrine\DBAL\Schema\Comparator;
11
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
12
use Doctrine\DBAL\Schema\Sequence;
13
use Doctrine\DBAL\Schema\Table;
14
use Doctrine\DBAL\Schema\TableDiff;
15
use Doctrine\DBAL\TransactionIsolationLevel;
16
use Doctrine\DBAL\Types\Type;
17
use UnexpectedValueException;
18
use function assert;
19
use function sprintf;
20
21
abstract class AbstractPostgreSqlPlatformTestCase extends AbstractPlatformTestCase
22
{
23
    /** @var PostgreSqlPlatform */
24
    protected $platform;
25
26
    public function getGenerateTableSql() : string
27
    {
28
        return 'CREATE TABLE test (id SERIAL NOT NULL, test VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))';
29
    }
30
31
    /**
32
     * {@inheritDoc}
33
     */
34
    public function getGenerateTableWithMultiColumnUniqueIndexSql() : array
35
    {
36
        return [
37
            'CREATE TABLE test (foo VARCHAR(255) DEFAULT NULL, bar VARCHAR(255) DEFAULT NULL)',
38
            'CREATE UNIQUE INDEX UNIQ_D87F7E0C8C73652176FF8CAA ON test (foo, bar)',
39
        ];
40
    }
41
42
    /**
43
     * {@inheritDoc}
44
     */
45
    public function getGenerateAlterTableSql() : array
46
    {
47
        return [
48
            'ALTER TABLE mytable ADD quota INT DEFAULT NULL',
49
            'ALTER TABLE mytable DROP foo',
50
            'ALTER TABLE mytable ALTER bar TYPE VARCHAR(255)',
51
            "ALTER TABLE mytable ALTER bar SET DEFAULT 'def'",
52
            'ALTER TABLE mytable ALTER bar SET NOT NULL',
53
            'ALTER TABLE mytable ALTER bloo TYPE BOOLEAN',
54
            "ALTER TABLE mytable ALTER bloo SET DEFAULT 'false'",
55
            'ALTER TABLE mytable ALTER bloo SET NOT NULL',
56
            'ALTER TABLE mytable RENAME TO userlist',
57
        ];
58
    }
59
60
    public function getGenerateIndexSql() : string
61
    {
62
        return 'CREATE INDEX my_idx ON mytable (user_name, last_login)';
63
    }
64
65
    public function getGenerateForeignKeySql() : string
66
    {
67
        return 'ALTER TABLE test ADD FOREIGN KEY (fk_name_id) REFERENCES other_table (id) NOT DEFERRABLE INITIALLY IMMEDIATE';
68
    }
69
70
    public function testGeneratesForeignKeySqlForNonStandardOptions() : void
71
    {
72
        $foreignKey = new ForeignKeyConstraint(
73
            ['foreign_id'],
74
            'my_table',
75
            ['id'],
76
            'my_fk',
77
            ['onDelete' => 'CASCADE']
78
        );
79
        self::assertEquals(
80
            'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE',
81
            $this->platform->getForeignKeyDeclarationSQL($foreignKey)
82
        );
83
84
        $foreignKey = new ForeignKeyConstraint(
85
            ['foreign_id'],
86
            'my_table',
87
            ['id'],
88
            'my_fk',
89
            ['match' => 'full']
90
        );
91
        self::assertEquals(
92
            'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) MATCH full NOT DEFERRABLE INITIALLY IMMEDIATE',
93
            $this->platform->getForeignKeyDeclarationSQL($foreignKey)
94
        );
95
96
        $foreignKey = new ForeignKeyConstraint(
97
            ['foreign_id'],
98
            'my_table',
99
            ['id'],
100
            'my_fk',
101
            ['deferrable' => true]
102
        );
103
        self::assertEquals(
104
            'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) DEFERRABLE INITIALLY IMMEDIATE',
105
            $this->platform->getForeignKeyDeclarationSQL($foreignKey)
106
        );
107
108
        $foreignKey = new ForeignKeyConstraint(
109
            ['foreign_id'],
110
            'my_table',
111
            ['id'],
112
            'my_fk',
113
            ['deferred' => true]
114
        );
115
        self::assertEquals(
116
            'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) NOT DEFERRABLE INITIALLY DEFERRED',
117
            $this->platform->getForeignKeyDeclarationSQL($foreignKey)
118
        );
119
120
        $foreignKey = new ForeignKeyConstraint(
121
            ['foreign_id'],
122
            'my_table',
123
            ['id'],
124
            'my_fk',
125
            ['deferred' => true]
126
        );
127
        self::assertEquals(
128
            'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) NOT DEFERRABLE INITIALLY DEFERRED',
129
            $this->platform->getForeignKeyDeclarationSQL($foreignKey)
130
        );
131
132
        $foreignKey = new ForeignKeyConstraint(
133
            ['foreign_id'],
134
            'my_table',
135
            ['id'],
136
            'my_fk',
137
            ['deferrable' => true, 'deferred' => true, 'match' => 'full']
138
        );
139
        self::assertEquals(
140
            'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) MATCH full DEFERRABLE INITIALLY DEFERRED',
141
            $this->platform->getForeignKeyDeclarationSQL($foreignKey)
142
        );
143
    }
144
145
    public function testGeneratesSqlSnippets() : void
146
    {
147
        self::assertEquals('SIMILAR TO', $this->platform->getRegexpExpression(), 'Regular expression operator is not correct');
148
        self::assertEquals('"', $this->platform->getIdentifierQuoteCharacter(), 'Identifier quote character is not correct');
149
        self::assertEquals('column1 || column2 || column3', $this->platform->getConcatExpression('column1', 'column2', 'column3'), 'Concatenation expression is not correct');
150
        self::assertEquals('SUBSTRING(column FROM 5)', $this->platform->getSubstringExpression('column', '5'), 'Substring expression without length is not correct');
151
        self::assertEquals('SUBSTRING(column FROM 1 FOR 5)', $this->platform->getSubstringExpression('column', '1', '5'), 'Substring expression with length is not correct');
152
    }
153
154
    public function testGeneratesTransactionCommands() : void
155
    {
156
        self::assertEquals(
157
            'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ UNCOMMITTED',
158
            $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::READ_UNCOMMITTED)
159
        );
160
        self::assertEquals(
161
            'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED',
162
            $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::READ_COMMITTED)
163
        );
164
        self::assertEquals(
165
            'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ',
166
            $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::REPEATABLE_READ)
167
        );
168
        self::assertEquals(
169
            'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE',
170
            $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::SERIALIZABLE)
171
        );
172
    }
173
174
    public function testGeneratesDDLSnippets() : void
175
    {
176
        self::assertEquals('CREATE DATABASE foobar', $this->platform->getCreateDatabaseSQL('foobar'));
177
        self::assertEquals('DROP DATABASE foobar', $this->platform->getDropDatabaseSQL('foobar'));
178
        self::assertEquals('DROP TABLE foobar', $this->platform->getDropTableSQL('foobar'));
179
    }
180
181
    public function testGenerateTableWithAutoincrement() : void
182
    {
183
        $table  = new Table('autoinc_table');
184
        $column = $table->addColumn('id', 'integer');
185
        $column->setAutoincrement(true);
186
187
        self::assertEquals(['CREATE TABLE autoinc_table (id SERIAL NOT NULL)'], $this->platform->getCreateTableSQL($table));
188
    }
189
190
    /**
191
     * @return mixed[][]
192
     */
193
    public static function serialTypes() : iterable
194
    {
195
        return [
196
            ['integer', 'SERIAL'],
197
            ['bigint', 'BIGSERIAL'],
198
        ];
199
    }
200
201
    /**
202
     * @dataProvider serialTypes
203
     * @group 2906
204
     */
205
    public function testGenerateTableWithAutoincrementDoesNotSetDefault(string $type, string $definition) : void
206
    {
207
        $table  = new Table('autoinc_table_notnull');
208
        $column = $table->addColumn('id', $type);
209
        $column->setAutoIncrement(true);
210
        $column->setNotNull(false);
211
212
        $sql = $this->platform->getCreateTableSQL($table);
213
214
        self::assertEquals([sprintf('CREATE TABLE autoinc_table_notnull (id %s)', $definition)], $sql);
215
    }
216
217
    /**
218
     * @dataProvider serialTypes
219
     * @group 2906
220
     */
221
    public function testCreateTableWithAutoincrementAndNotNullAddsConstraint(string $type, string $definition) : void
222
    {
223
        $table  = new Table('autoinc_table_notnull_enabled');
224
        $column = $table->addColumn('id', $type);
225
        $column->setAutoIncrement(true);
226
        $column->setNotNull(true);
227
228
        $sql = $this->platform->getCreateTableSQL($table);
229
230
        self::assertEquals([sprintf('CREATE TABLE autoinc_table_notnull_enabled (id %s NOT NULL)', $definition)], $sql);
231
    }
232
233
    /**
234
     * @dataProvider serialTypes
235
     * @group 2906
236
     */
237
    public function testGetDefaultValueDeclarationSQLIgnoresTheDefaultKeyWhenTheFieldIsSerial(string $type) : void
238
    {
239
        $sql = $this->platform->getDefaultValueDeclarationSQL(
240
            [
241
                'autoincrement' => true,
242
                'type'          => Type::getType($type),
243
                'default'       => 1,
244
            ]
245
        );
246
247
        self::assertSame('', $sql);
248
    }
249
250
    public function testGeneratesTypeDeclarationForIntegers() : void
251
    {
252
        self::assertEquals(
253
            'INT',
254
            $this->platform->getIntegerTypeDeclarationSQL([])
255
        );
256
        self::assertEquals(
257
            'SERIAL',
258
            $this->platform->getIntegerTypeDeclarationSQL(['autoincrement' => true])
259
        );
260
        self::assertEquals(
261
            'SERIAL',
262
            $this->platform->getIntegerTypeDeclarationSQL(
263
                ['autoincrement' => true, 'primary' => true]
264
            )
265
        );
266
    }
267
268
    public function getGenerateUniqueIndexSql() : string
269
    {
270
        return 'CREATE UNIQUE INDEX index_name ON test (test, test2)';
271
    }
272
273
    public function testGeneratesSequenceSqlCommands() : void
274
    {
275
        $sequence = new Sequence('myseq', 20, 1);
276
        self::assertEquals(
277
            'CREATE SEQUENCE myseq INCREMENT BY 20 MINVALUE 1 START 1',
278
            $this->platform->getCreateSequenceSQL($sequence)
279
        );
280
        self::assertEquals(
281
            'DROP SEQUENCE myseq CASCADE',
282
            $this->platform->getDropSequenceSQL('myseq')
283
        );
284
        self::assertEquals(
285
            "SELECT NEXTVAL('myseq')",
286
            $this->platform->getSequenceNextValSQL('myseq')
287
        );
288
    }
289
290
    public function testDoesNotPreferIdentityColumns() : void
291
    {
292
        self::assertFalse($this->platform->prefersIdentityColumns());
293
    }
294
295
    public function testPrefersSequences() : void
296
    {
297
        self::assertTrue($this->platform->prefersSequences());
298
    }
299
300
    public function testSupportsIdentityColumns() : void
301
    {
302
        self::assertTrue($this->platform->supportsIdentityColumns());
303
    }
304
305
    public function testSupportsSavePoints() : void
306
    {
307
        self::assertTrue($this->platform->supportsSavepoints());
308
    }
309
310
    public function testSupportsSequences() : void
311
    {
312
        self::assertTrue($this->platform->supportsSequences());
313
    }
314
315
    /**
316
     * {@inheritdoc}
317
     */
318
    protected function supportsCommentOnStatement() : bool
319
    {
320
        return true;
321
    }
322
323
    public function testModifyLimitQuery() : void
324
    {
325
        $sql = $this->platform->modifyLimitQuery('SELECT * FROM user', 10, 0);
326
        self::assertEquals('SELECT * FROM user LIMIT 10', $sql);
327
    }
328
329
    public function testModifyLimitQueryWithEmptyOffset() : void
330
    {
331
        $sql = $this->platform->modifyLimitQuery('SELECT * FROM user', 10);
332
        self::assertEquals('SELECT * FROM user LIMIT 10', $sql);
333
    }
334
335
    /**
336
     * {@inheritDoc}
337
     */
338
    public function getCreateTableColumnCommentsSQL() : array
339
    {
340
        return [
341
            'CREATE TABLE test (id INT NOT NULL, PRIMARY KEY(id))',
342
            "COMMENT ON COLUMN test.id IS 'This is a comment'",
343
        ];
344
    }
345
346
    /**
347
     * {@inheritDoc}
348
     */
349
    public function getAlterTableColumnCommentsSQL() : array
350
    {
351
        return [
352
            'ALTER TABLE mytable ADD quota INT NOT NULL',
353
            "COMMENT ON COLUMN mytable.quota IS 'A comment'",
354
            'COMMENT ON COLUMN mytable.foo IS NULL',
355
            "COMMENT ON COLUMN mytable.baz IS 'B comment'",
356
        ];
357
    }
358
359
    /**
360
     * {@inheritDoc}
361
     */
362
    public function getCreateTableColumnTypeCommentsSQL() : array
363
    {
364
        return [
365
            'CREATE TABLE test (id INT NOT NULL, data TEXT NOT NULL, PRIMARY KEY(id))',
366
            "COMMENT ON COLUMN test.data IS '(DC2Type:array)'",
367
        ];
368
    }
369
370
    /**
371
     * {@inheritDoc}
372
     */
373
    protected function getQuotedColumnInPrimaryKeySQL() : array
374
    {
375
        return ['CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL, PRIMARY KEY("create"))'];
376
    }
377
378
    /**
379
     * {@inheritDoc}
380
     */
381
    protected function getQuotedColumnInIndexSQL() : array
382
    {
383
        return [
384
            'CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL)',
385
            'CREATE INDEX IDX_22660D028FD6E0FB ON "quoted" ("create")',
386
        ];
387
    }
388
389
    /**
390
     * {@inheritDoc}
391
     */
392
    protected function getQuotedNameInIndexSQL() : array
393
    {
394
        return [
395
            'CREATE TABLE test (column1 VARCHAR(255) NOT NULL)',
396
            'CREATE INDEX "key" ON test (column1)',
397
        ];
398
    }
399
400
    /**
401
     * {@inheritDoc}
402
     */
403
    protected function getQuotedColumnInForeignKeySQL() : array
404
    {
405
        return [
406
            'CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL, foo VARCHAR(255) NOT NULL, "bar" VARCHAR(255) NOT NULL)',
407
            'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_RESERVED_KEYWORD FOREIGN KEY ("create", foo, "bar") REFERENCES "foreign" ("create", bar, "foo-bar") NOT DEFERRABLE INITIALLY IMMEDIATE',
408
            'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_NON_RESERVED_KEYWORD FOREIGN KEY ("create", foo, "bar") REFERENCES foo ("create", bar, "foo-bar") NOT DEFERRABLE INITIALLY IMMEDIATE',
409
            'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_INTENDED_QUOTATION FOREIGN KEY ("create", foo, "bar") REFERENCES "foo-bar" ("create", bar, "foo-bar") NOT DEFERRABLE INITIALLY IMMEDIATE',
410
        ];
411
    }
412
413
    /**
414
     * @group DBAL-457
415
     * @dataProvider pgBooleanProvider
416
     */
417
    public function testConvertBooleanAsLiteralStrings(
418
        string $databaseValue,
419
        string $preparedStatementValue,
420
        int $integerValue,
0 ignored issues
show
Unused Code introduced by
The parameter $integerValue is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

420
        /** @scrutinizer ignore-unused */ int $integerValue,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
421
        bool $booleanValue
0 ignored issues
show
Unused Code introduced by
The parameter $booleanValue is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

421
        /** @scrutinizer ignore-unused */ bool $booleanValue

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
422
    ) : void {
423
        self::assertEquals($preparedStatementValue, $this->platform->convertBooleans($databaseValue));
424
    }
425
426
    /**
427
     * @group DBAL-457
428
     */
429
    public function testConvertBooleanAsLiteralIntegers() : void
430
    {
431
        $this->platform->setUseBooleanTrueFalseStrings(false);
432
433
        self::assertEquals(1, $this->platform->convertBooleans(true));
434
        self::assertEquals(1, $this->platform->convertBooleans('1'));
435
436
        self::assertEquals(0, $this->platform->convertBooleans(false));
437
        self::assertEquals(0, $this->platform->convertBooleans('0'));
438
    }
439
440
    /**
441
     * @group DBAL-630
442
     * @dataProvider pgBooleanProvider
443
     */
444
    public function testConvertBooleanAsDatabaseValueStrings(
445
        string $databaseValue,
0 ignored issues
show
Unused Code introduced by
The parameter $databaseValue is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

445
        /** @scrutinizer ignore-unused */ string $databaseValue,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
446
        string $preparedStatementValue,
0 ignored issues
show
Unused Code introduced by
The parameter $preparedStatementValue is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

446
        /** @scrutinizer ignore-unused */ string $preparedStatementValue,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
447
        int $integerValue,
448
        bool $booleanValue
449
    ) : void {
450
        self::assertSame($integerValue, $this->platform->convertBooleansToDatabaseValue($booleanValue));
451
    }
452
453
    /**
454
     * @group DBAL-630
455
     */
456
    public function testConvertBooleanAsDatabaseValueIntegers() : void
457
    {
458
        $this->platform->setUseBooleanTrueFalseStrings(false);
459
460
        self::assertSame(1, $this->platform->convertBooleansToDatabaseValue(true));
461
        self::assertSame(0, $this->platform->convertBooleansToDatabaseValue(false));
462
    }
463
464
    /**
465
     * @dataProvider pgBooleanProvider
466
     */
467
    public function testConvertFromBoolean(string $databaseValue, string $prepareStatementValue, int $integerValue, bool $booleanValue) : void
0 ignored issues
show
Unused Code introduced by
The parameter $integerValue is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

467
    public function testConvertFromBoolean(string $databaseValue, string $prepareStatementValue, /** @scrutinizer ignore-unused */ int $integerValue, bool $booleanValue) : void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $prepareStatementValue is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

467
    public function testConvertFromBoolean(string $databaseValue, /** @scrutinizer ignore-unused */ string $prepareStatementValue, int $integerValue, bool $booleanValue) : void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
468
    {
469
        self::assertSame($booleanValue, $this->platform->convertFromBoolean($databaseValue));
470
    }
471
472
    public function testThrowsExceptionWithInvalidBooleanLiteral() : void
473
    {
474
        $this->expectException(UnexpectedValueException::class);
475
        $this->expectExceptionMessage('Unrecognized boolean literal, my-bool given.');
476
477
        $this->platform->convertBooleansToDatabaseValue('my-bool');
478
    }
479
480
    public function testGetCreateSchemaSQL() : void
481
    {
482
        $schemaName = 'schema';
483
        $sql        = $this->platform->getCreateSchemaSQL($schemaName);
484
        self::assertEquals('CREATE SCHEMA ' . $schemaName, $sql);
485
    }
486
487
    public function testAlterDecimalPrecisionScale() : void
488
    {
489
        $table = new Table('mytable');
490
        $table->addColumn('dfoo1', 'decimal');
491
        $table->addColumn('dfoo2', 'decimal', ['precision' => 10, 'scale' => 6]);
492
        $table->addColumn('dfoo3', 'decimal', ['precision' => 10, 'scale' => 6]);
493
        $table->addColumn('dfoo4', 'decimal', ['precision' => 10, 'scale' => 6]);
494
495
        $tableDiff            = new TableDiff('mytable');
496
        $tableDiff->fromTable = $table;
497
498
        $tableDiff->changedColumns['dloo1'] = new ColumnDiff(
499
            'dloo1',
500
            new Column(
501
                'dloo1',
502
                Type::getType('decimal'),
503
                ['precision' => 16, 'scale' => 6]
504
            ),
505
            ['precision']
506
        );
507
        $tableDiff->changedColumns['dloo2'] = new ColumnDiff(
508
            'dloo2',
509
            new Column(
510
                'dloo2',
511
                Type::getType('decimal'),
512
                ['precision' => 10, 'scale' => 4]
513
            ),
514
            ['scale']
515
        );
516
        $tableDiff->changedColumns['dloo3'] = new ColumnDiff(
517
            'dloo3',
518
            new Column(
519
                'dloo3',
520
                Type::getType('decimal'),
521
                ['precision' => 10, 'scale' => 6]
522
            ),
523
            []
524
        );
525
        $tableDiff->changedColumns['dloo4'] = new ColumnDiff(
526
            'dloo4',
527
            new Column(
528
                'dloo4',
529
                Type::getType('decimal'),
530
                ['precision' => 16, 'scale' => 8]
531
            ),
532
            ['precision', 'scale']
533
        );
534
535
        $sql = $this->platform->getAlterTableSQL($tableDiff);
536
537
        $expectedSql = [
538
            'ALTER TABLE mytable ALTER dloo1 TYPE NUMERIC(16, 6)',
539
            'ALTER TABLE mytable ALTER dloo2 TYPE NUMERIC(10, 4)',
540
            'ALTER TABLE mytable ALTER dloo4 TYPE NUMERIC(16, 8)',
541
        ];
542
543
        self::assertEquals($expectedSql, $sql);
544
    }
545
546
    /**
547
     * @group DBAL-365
548
     */
549
    public function testDroppingConstraintsBeforeColumns() : void
550
    {
551
        $newTable = new Table('mytable');
552
        $newTable->addColumn('id', 'integer');
553
        $newTable->setPrimaryKey(['id']);
554
555
        $oldTable = clone $newTable;
556
        $oldTable->addColumn('parent_id', 'integer');
557
        $oldTable->addForeignKeyConstraint('mytable', ['parent_id'], ['id']);
558
559
        $comparator = new Comparator();
560
        $tableDiff  = $comparator->diffTable($oldTable, $newTable);
561
562
        self::assertNotNull($tableDiff);
563
564
        $sql = $this->platform->getAlterTableSQL($tableDiff);
565
566
        $expectedSql = [
567
            'ALTER TABLE mytable DROP CONSTRAINT FK_6B2BD609727ACA70',
568
            'DROP INDEX IDX_6B2BD609727ACA70',
569
            'ALTER TABLE mytable DROP parent_id',
570
        ];
571
572
        self::assertEquals($expectedSql, $sql);
573
    }
574
575
    /**
576
     * @group DBAL-563
577
     */
578
    public function testReturnsIdentitySequenceName() : void
579
    {
580
        self::assertSame('mytable_mycolumn_seq', $this->platform->getIdentitySequenceName('mytable', 'mycolumn'));
581
    }
582
583
    /**
584
     * @dataProvider dataCreateSequenceWithCache
585
     * @group DBAL-139
586
     */
587
    public function testCreateSequenceWithCache(int $cacheSize, string $expectedSql) : void
588
    {
589
        $sequence = new Sequence('foo', 1, 1, $cacheSize);
590
        self::assertStringContainsString($expectedSql, $this->platform->getCreateSequenceSQL($sequence));
591
    }
592
593
    /**
594
     * @return mixed[][]
595
     */
596
    public static function dataCreateSequenceWithCache() : iterable
597
    {
598
        return [
599
            [3, 'CACHE 3'],
600
        ];
601
    }
602
603
    public function getExpectedFixedLengthBinaryTypeDeclarationSQLNoLength() : string
604
    {
605
        return 'BYTEA';
606
    }
607
608
    public function getExpectedFixedLengthBinaryTypeDeclarationSQLWithLength() : string
609
    {
610
        return 'BYTEA';
611
    }
612
613
    public function getExpectedVariableLengthBinaryTypeDeclarationSQLNoLength() : string
614
    {
615
        return 'BYTEA';
616
    }
617
618
    public function getExpectedVariableLengthBinaryTypeDeclarationSQLWithLength() : string
619
    {
620
        return 'BYTEA';
621
    }
622
623
    public function testDoesNotPropagateUnnecessaryTableAlterationOnBinaryType() : void
624
    {
625
        $table1 = new Table('mytable');
626
        $table1->addColumn('column_varbinary', 'binary');
627
        $table1->addColumn('column_binary', 'binary', ['fixed' => true]);
628
        $table1->addColumn('column_blob', 'blob');
629
630
        $table2 = new Table('mytable');
631
        $table2->addColumn('column_varbinary', 'binary', ['fixed' => true]);
632
        $table2->addColumn('column_binary', 'binary');
633
        $table2->addColumn('column_blob', 'binary');
634
635
        $comparator = new Comparator();
636
637
        $diff = $comparator->diffTable($table1, $table2);
638
639
        self::assertNotNull($diff);
640
641
        // VARBINARY -> BINARY
642
        // BINARY    -> VARBINARY
643
        // BLOB      -> VARBINARY
644
        self::assertEmpty($this->platform->getAlterTableSQL($diff));
645
646
        $table2 = new Table('mytable');
647
        $table2->addColumn('column_varbinary', 'binary', ['length' => 42]);
648
        $table2->addColumn('column_binary', 'blob');
649
        $table2->addColumn('column_blob', 'binary', ['length' => 11, 'fixed' => true]);
650
651
        $diff = $comparator->diffTable($table1, $table2);
652
653
        self::assertNotNull($diff);
654
655
        // VARBINARY -> VARBINARY with changed length
656
        // BINARY    -> BLOB
657
        // BLOB      -> BINARY
658
        self::assertEmpty($this->platform->getAlterTableSQL($diff));
659
660
        $table2 = new Table('mytable');
661
        $table2->addColumn('column_varbinary', 'blob');
662
        $table2->addColumn('column_binary', 'binary', ['length' => 42, 'fixed' => true]);
663
        $table2->addColumn('column_blob', 'blob');
664
665
        $diff = $comparator->diffTable($table1, $table2);
666
667
        self::assertNotNull($diff);
668
669
        // VARBINARY -> BLOB
670
        // BINARY    -> BINARY with changed length
671
        // BLOB      -> BLOB
672
        self::assertEmpty($this->platform->getAlterTableSQL($diff));
673
    }
674
675
    /**
676
     * {@inheritDoc}
677
     *
678
     * @group DBAL-234
679
     */
680
    protected function getAlterTableRenameIndexSQL() : array
681
    {
682
        return ['ALTER INDEX idx_foo RENAME TO idx_bar'];
683
    }
684
685
    /**
686
     * {@inheritDoc}
687
     *
688
     * @group DBAL-234
689
     */
690
    protected function getQuotedAlterTableRenameIndexSQL() : array
691
    {
692
        return [
693
            'ALTER INDEX "create" RENAME TO "select"',
694
            'ALTER INDEX "foo" RENAME TO "bar"',
695
        ];
696
    }
697
698
    /**
699
     * PostgreSQL boolean strings provider
700
     *
701
     * @return mixed[][]
702
     */
703
    public static function pgBooleanProvider() : iterable
704
    {
705
        return [
706
            // Database value, prepared statement value, boolean integer value, boolean value.
707
            ['t', 'true', 1, true],
708
            ['true', 'true', 1, true],
709
            ['y', 'true', 1, true],
710
            ['yes', 'true', 1, true],
711
            ['on', 'true', 1, true],
712
            ['1', 'true', 1, true],
713
714
            ['f', 'false', 0, false],
715
            ['false', 'false', 0, false],
716
            [ 'n', 'false', 0, false],
717
            ['no', 'false', 0, false],
718
            ['off', 'false', 0, false],
719
            ['0', 'false', 0, false],
720
        ];
721
    }
722
723
    /**
724
     * {@inheritdoc}
725
     */
726
    protected function getQuotedAlterTableRenameColumnSQL() : array
727
    {
728
        return [
729
            'ALTER TABLE mytable RENAME COLUMN unquoted1 TO unquoted',
730
            'ALTER TABLE mytable RENAME COLUMN unquoted2 TO "where"',
731
            'ALTER TABLE mytable RENAME COLUMN unquoted3 TO "foo"',
732
            'ALTER TABLE mytable RENAME COLUMN "create" TO reserved_keyword',
733
            'ALTER TABLE mytable RENAME COLUMN "table" TO "from"',
734
            'ALTER TABLE mytable RENAME COLUMN "select" TO "bar"',
735
            'ALTER TABLE mytable RENAME COLUMN quoted1 TO quoted',
736
            'ALTER TABLE mytable RENAME COLUMN quoted2 TO "and"',
737
            'ALTER TABLE mytable RENAME COLUMN quoted3 TO "baz"',
738
        ];
739
    }
740
741
    /**
742
     * {@inheritdoc}
743
     */
744
    protected function getQuotedAlterTableChangeColumnLengthSQL() : array
745
    {
746
        return [
747
            'ALTER TABLE mytable ALTER unquoted1 TYPE VARCHAR(255)',
748
            'ALTER TABLE mytable ALTER unquoted2 TYPE VARCHAR(255)',
749
            'ALTER TABLE mytable ALTER unquoted3 TYPE VARCHAR(255)',
750
            'ALTER TABLE mytable ALTER "create" TYPE VARCHAR(255)',
751
            'ALTER TABLE mytable ALTER "table" TYPE VARCHAR(255)',
752
            'ALTER TABLE mytable ALTER "select" TYPE VARCHAR(255)',
753
        ];
754
    }
755
756
    /**
757
     * {@inheritDoc}
758
     *
759
     * @group DBAL-807
760
     */
761
    protected function getAlterTableRenameIndexInSchemaSQL() : array
762
    {
763
        return ['ALTER INDEX myschema.idx_foo RENAME TO idx_bar'];
764
    }
765
766
    /**
767
     * {@inheritDoc}
768
     *
769
     * @group DBAL-807
770
     */
771
    protected function getQuotedAlterTableRenameIndexInSchemaSQL() : array
772
    {
773
        return [
774
            'ALTER INDEX "schema"."create" RENAME TO "select"',
775
            'ALTER INDEX "schema"."foo" RENAME TO "bar"',
776
        ];
777
    }
778
779
    protected function getQuotesDropForeignKeySQL() : string
780
    {
781
        return 'ALTER TABLE "table" DROP CONSTRAINT "select"';
782
    }
783
784
    /**
785
     * @group DBAL-423
786
     */
787
    public function testReturnsGuidTypeDeclarationSQL() : void
788
    {
789
        self::assertSame('UUID', $this->platform->getGuidTypeDeclarationSQL([]));
790
    }
791
792
    /**
793
     * {@inheritdoc}
794
     */
795
    public function getAlterTableRenameColumnSQL() : array
796
    {
797
        return ['ALTER TABLE foo RENAME COLUMN bar TO baz'];
798
    }
799
800
    /**
801
     * {@inheritdoc}
802
     */
803
    protected function getQuotesTableIdentifiersInAlterTableSQL() : array
804
    {
805
        return [
806
            'ALTER TABLE "foo" DROP CONSTRAINT fk1',
807
            'ALTER TABLE "foo" DROP CONSTRAINT fk2',
808
            'ALTER TABLE "foo" ADD bloo INT NOT NULL',
809
            'ALTER TABLE "foo" DROP baz',
810
            'ALTER TABLE "foo" ALTER bar DROP NOT NULL',
811
            'ALTER TABLE "foo" RENAME COLUMN id TO war',
812
            'ALTER TABLE "foo" RENAME TO "table"',
813
            'ALTER TABLE "table" ADD CONSTRAINT fk_add FOREIGN KEY (fk3) REFERENCES fk_table (id) NOT DEFERRABLE ' .
814
            'INITIALLY IMMEDIATE',
815
            'ALTER TABLE "table" ADD CONSTRAINT fk2 FOREIGN KEY (fk2) REFERENCES fk_table2 (id) NOT DEFERRABLE ' .
816
            'INITIALLY IMMEDIATE',
817
        ];
818
    }
819
820
    /**
821
     * {@inheritdoc}
822
     */
823
    protected function getCommentOnColumnSQL() : array
824
    {
825
        return [
826
            'COMMENT ON COLUMN foo.bar IS \'comment\'',
827
            'COMMENT ON COLUMN "Foo"."BAR" IS \'comment\'',
828
            'COMMENT ON COLUMN "select"."from" IS \'comment\'',
829
        ];
830
    }
831
832
    /**
833
     * @group DBAL-1004
834
     */
835
    public function testAltersTableColumnCommentWithExplicitlyQuotedIdentifiers() : void
836
    {
837
        $table1 = new Table('"foo"', [new Column('"bar"', Type::getType('integer'))]);
838
        $table2 = new Table('"foo"', [new Column('"bar"', Type::getType('integer'), ['comment' => 'baz'])]);
839
840
        $comparator = new Comparator();
841
842
        $tableDiff = $comparator->diffTable($table1, $table2);
843
844
        self::assertInstanceOf(TableDiff::class, $tableDiff);
845
        self::assertSame(
846
            ['COMMENT ON COLUMN "foo"."bar" IS \'baz\''],
847
            $this->platform->getAlterTableSQL($tableDiff)
848
        );
849
    }
850
851
    /**
852
     * @group 3158
853
     */
854
    public function testAltersTableColumnCommentIfRequiredByType() : void
855
    {
856
        $table1 = new Table('"foo"', [new Column('"bar"', Type::getType('datetime'))]);
857
        $table2 = new Table('"foo"', [new Column('"bar"', Type::getType('datetime_immutable'))]);
858
859
        $comparator = new Comparator();
860
861
        $tableDiff = $comparator->diffTable($table1, $table2);
862
863
        $this->assertInstanceOf('Doctrine\DBAL\Schema\TableDiff', $tableDiff);
864
        $this->assertSame(
865
            [
866
                'ALTER TABLE "foo" ALTER "bar" TYPE TIMESTAMP(0) WITHOUT TIME ZONE',
867
                'ALTER TABLE "foo" ALTER "bar" DROP DEFAULT',
868
                'COMMENT ON COLUMN "foo"."bar" IS \'(DC2Type:datetime_immutable)\'',
869
            ],
870
            $this->platform->getAlterTableSQL($tableDiff)
871
        );
872
    }
873
874
    /**
875
     * {@inheritdoc}
876
     */
877
    protected function getQuotesReservedKeywordInUniqueConstraintDeclarationSQL() : string
878
    {
879
        return 'CONSTRAINT "select" UNIQUE (foo)';
880
    }
881
882
    /**
883
     * {@inheritdoc}
884
     */
885
    protected function getQuotesReservedKeywordInIndexDeclarationSQL() : string
886
    {
887
        return 'INDEX "select" (foo)';
888
    }
889
890
    /**
891
     * {@inheritdoc}
892
     */
893
    protected function getQuotesReservedKeywordInTruncateTableSQL() : string
894
    {
895
        return 'TRUNCATE "select"';
896
    }
897
898
    /**
899
     * {@inheritdoc}
900
     */
901
    protected function getAlterStringToFixedStringSQL() : array
902
    {
903
        return ['ALTER TABLE mytable ALTER name TYPE CHAR(2)'];
904
    }
905
906
    /**
907
     * {@inheritdoc}
908
     */
909
    protected function getGeneratesAlterTableRenameIndexUsedByForeignKeySQL() : array
910
    {
911
        return ['ALTER INDEX idx_foo RENAME TO idx_foo_renamed'];
912
    }
913
914
    /**
915
     * @group DBAL-1142
916
     */
917
    public function testInitializesTsvectorTypeMapping() : void
918
    {
919
        self::assertTrue($this->platform->hasDoctrineTypeMappingFor('tsvector'));
920
        self::assertEquals('text', $this->platform->getDoctrineTypeMapping('tsvector'));
921
    }
922
923
    /**
924
     * @group DBAL-1220
925
     */
926
    public function testReturnsDisallowDatabaseConnectionsSQL() : void
927
    {
928
        assert($this->platform instanceof PostgreSqlPlatform);
929
930
        self::assertSame(
931
            "UPDATE pg_database SET datallowconn = 'false' WHERE datname = 'foo'",
932
            $this->platform->getDisallowDatabaseConnectionsSQL('foo')
933
        );
934
    }
935
936
    /**
937
     * @group DBAL-1220
938
     */
939
    public function testReturnsCloseActiveDatabaseConnectionsSQL() : void
940
    {
941
        assert($this->platform instanceof PostgreSqlPlatform);
942
943
        self::assertSame(
944
            "SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname = 'foo'",
945
            $this->platform->getCloseActiveDatabaseConnectionsSQL('foo')
946
        );
947
    }
948
949
    /**
950
     * @group DBAL-2436
951
     */
952
    public function testQuotesTableNameInListTableForeignKeysSQL() : void
953
    {
954
        self::assertStringContainsStringIgnoringCase(
955
            "'Foo''Bar\\'",
956
            $this->platform->getListTableForeignKeysSQL("Foo'Bar\\")
957
        );
958
    }
959
960
    /**
961
     * @group DBAL-2436
962
     */
963
    public function testQuotesSchemaNameInListTableForeignKeysSQL() : void
964
    {
965
        self::assertStringContainsStringIgnoringCase(
966
            "'Foo''Bar\\'",
967
            $this->platform->getListTableForeignKeysSQL("Foo'Bar\\.baz_table")
968
        );
969
    }
970
971
    /**
972
     * @group DBAL-2436
973
     */
974
    public function testQuotesTableNameInListTableConstraintsSQL() : void
975
    {
976
        self::assertStringContainsStringIgnoringCase(
977
            "'Foo''Bar\\'",
978
            $this->platform->getListTableConstraintsSQL("Foo'Bar\\")
979
        );
980
    }
981
982
    /**
983
     * @group DBAL-2436
984
     */
985
    public function testQuotesTableNameInListTableIndexesSQL() : void
986
    {
987
        self::assertStringContainsStringIgnoringCase(
988
            "'Foo''Bar\\'",
989
            $this->platform->getListTableIndexesSQL("Foo'Bar\\")
990
        );
991
    }
992
993
    /**
994
     * @group DBAL-2436
995
     */
996
    public function testQuotesSchemaNameInListTableIndexesSQL() : void
997
    {
998
        self::assertStringContainsStringIgnoringCase(
999
            "'Foo''Bar\\'",
1000
            $this->platform->getListTableIndexesSQL("Foo'Bar\\.baz_table")
1001
        );
1002
    }
1003
1004
    /**
1005
     * @group DBAL-2436
1006
     */
1007
    public function testQuotesTableNameInListTableColumnsSQL() : void
1008
    {
1009
        self::assertStringContainsStringIgnoringCase(
1010
            "'Foo''Bar\\'",
1011
            $this->platform->getListTableColumnsSQL("Foo'Bar\\")
1012
        );
1013
    }
1014
1015
    /**
1016
     * @group DBAL-2436
1017
     */
1018
    public function testQuotesSchemaNameInListTableColumnsSQL() : void
1019
    {
1020
        self::assertStringContainsStringIgnoringCase(
1021
            "'Foo''Bar\\'",
1022
            $this->platform->getListTableColumnsSQL("Foo'Bar\\.baz_table")
1023
        );
1024
    }
1025
1026
    /**
1027
     * @group DBAL-2436
1028
     */
1029
    public function testQuotesDatabaseNameInCloseActiveDatabaseConnectionsSQL() : void
1030
    {
1031
        assert($this->platform instanceof PostgreSqlPlatform);
1032
1033
        self::assertStringContainsStringIgnoringCase(
1034
            "'Foo''Bar\\'",
1035
            $this->platform->getCloseActiveDatabaseConnectionsSQL("Foo'Bar\\")
1036
        );
1037
    }
1038
}
1039