AbstractPostgreSQLPlatformTestCase.php 32.3 KB
Newer Older
1 2
<?php

Michael Moravec's avatar
Michael Moravec committed
3 4
declare(strict_types=1);

5
namespace Doctrine\DBAL\Tests\Platforms;
6

Sergei Morozov's avatar
Sergei Morozov committed
7
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
8
use Doctrine\DBAL\Schema\Column;
Sergei Morozov's avatar
Sergei Morozov committed
9
use Doctrine\DBAL\Schema\ColumnDiff;
10
use Doctrine\DBAL\Schema\Comparator;
Sergei Morozov's avatar
Sergei Morozov committed
11 12
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Sequence;
13 14
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
15
use Doctrine\DBAL\TransactionIsolationLevel;
16
use Doctrine\DBAL\Types\Type;
17
use UnexpectedValueException;
Sergei Morozov's avatar
Sergei Morozov committed
18
use function sprintf;
19

20
abstract class AbstractPostgreSQLPlatformTestCase extends AbstractPlatformTestCase
21
{
Sergei Morozov's avatar
Sergei Morozov committed
22
    /** @var PostgreSQL94Platform */
23 24
    protected $platform;

25
    public function getGenerateTableSql() : string
26 27 28 29
    {
        return 'CREATE TABLE test (id SERIAL NOT NULL, test VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))';
    }

30 31 32 33
    /**
     * {@inheritDoc}
     */
    public function getGenerateTableWithMultiColumnUniqueIndexSql() : array
34
    {
Sergei Morozov's avatar
Sergei Morozov committed
35
        return [
36
            'CREATE TABLE test (foo VARCHAR(255) DEFAULT NULL, bar VARCHAR(255) DEFAULT NULL)',
Sergei Morozov's avatar
Sergei Morozov committed
37 38
            'CREATE UNIQUE INDEX UNIQ_D87F7E0C8C73652176FF8CAA ON test (foo, bar)',
        ];
39 40
    }

41 42 43 44
    /**
     * {@inheritDoc}
     */
    public function getGenerateAlterTableSql() : array
45
    {
Sergei Morozov's avatar
Sergei Morozov committed
46
        return [
47 48 49 50 51 52 53 54 55
            'ALTER TABLE mytable ADD quota INT DEFAULT NULL',
            'ALTER TABLE mytable DROP foo',
            'ALTER TABLE mytable ALTER bar TYPE VARCHAR(255)',
            "ALTER TABLE mytable ALTER bar SET DEFAULT 'def'",
            'ALTER TABLE mytable ALTER bar SET NOT NULL',
            'ALTER TABLE mytable ALTER bloo TYPE BOOLEAN',
            "ALTER TABLE mytable ALTER bloo SET DEFAULT 'false'",
            'ALTER TABLE mytable ALTER bloo SET NOT NULL',
            'ALTER TABLE mytable RENAME TO userlist',
Sergei Morozov's avatar
Sergei Morozov committed
56
        ];
57 58
    }

59
    public function getGenerateIndexSql() : string
60 61 62 63
    {
        return 'CREATE INDEX my_idx ON mytable (user_name, last_login)';
    }

64
    public function getGenerateForeignKeySql() : string
65 66 67 68
    {
        return 'ALTER TABLE test ADD FOREIGN KEY (fk_name_id) REFERENCES other_table (id) NOT DEFERRABLE INITIALLY IMMEDIATE';
    }

69
    public function testGeneratesForeignKeySqlForNonStandardOptions() : void
70
    {
Sergei Morozov's avatar
Sergei Morozov committed
71 72 73 74 75 76
        $foreignKey = new ForeignKeyConstraint(
            ['foreign_id'],
            'my_table',
            ['id'],
            'my_fk',
            ['onDelete' => 'CASCADE']
77
        );
78
        self::assertEquals(
Sergei Morozov's avatar
Sergei Morozov committed
79
            'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE',
Sergei Morozov's avatar
Sergei Morozov committed
80
            $this->platform->getForeignKeyDeclarationSQL($foreignKey)
81 82
        );

Sergei Morozov's avatar
Sergei Morozov committed
83 84 85 86 87 88
        $foreignKey = new ForeignKeyConstraint(
            ['foreign_id'],
            'my_table',
            ['id'],
            'my_fk',
            ['match' => 'full']
89
        );
90
        self::assertEquals(
Sergei Morozov's avatar
Sergei Morozov committed
91
            'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) MATCH full NOT DEFERRABLE INITIALLY IMMEDIATE',
Sergei Morozov's avatar
Sergei Morozov committed
92
            $this->platform->getForeignKeyDeclarationSQL($foreignKey)
93 94
        );

Sergei Morozov's avatar
Sergei Morozov committed
95 96 97 98 99 100
        $foreignKey = new ForeignKeyConstraint(
            ['foreign_id'],
            'my_table',
            ['id'],
            'my_fk',
            ['deferrable' => true]
101
        );
102
        self::assertEquals(
Sergei Morozov's avatar
Sergei Morozov committed
103
            'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) DEFERRABLE INITIALLY IMMEDIATE',
Sergei Morozov's avatar
Sergei Morozov committed
104
            $this->platform->getForeignKeyDeclarationSQL($foreignKey)
105 106
        );

Sergei Morozov's avatar
Sergei Morozov committed
107 108 109 110 111 112
        $foreignKey = new ForeignKeyConstraint(
            ['foreign_id'],
            'my_table',
            ['id'],
            'my_fk',
            ['deferred' => true]
113
        );
114
        self::assertEquals(
Sergei Morozov's avatar
Sergei Morozov committed
115
            'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) NOT DEFERRABLE INITIALLY DEFERRED',
Sergei Morozov's avatar
Sergei Morozov committed
116
            $this->platform->getForeignKeyDeclarationSQL($foreignKey)
117 118
        );

Sergei Morozov's avatar
Sergei Morozov committed
119 120 121 122 123
        $foreignKey = new ForeignKeyConstraint(
            ['foreign_id'],
            'my_table',
            ['id'],
            'my_fk',
124
            ['deferred' => true]
125
        );
126
        self::assertEquals(
Sergei Morozov's avatar
Sergei Morozov committed
127
            'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) NOT DEFERRABLE INITIALLY DEFERRED',
Sergei Morozov's avatar
Sergei Morozov committed
128
            $this->platform->getForeignKeyDeclarationSQL($foreignKey)
129 130
        );

Sergei Morozov's avatar
Sergei Morozov committed
131 132 133 134 135 136
        $foreignKey = new ForeignKeyConstraint(
            ['foreign_id'],
            'my_table',
            ['id'],
            'my_fk',
            ['deferrable' => true, 'deferred' => true, 'match' => 'full']
137
        );
138
        self::assertEquals(
Sergei Morozov's avatar
Sergei Morozov committed
139
            'CONSTRAINT my_fk FOREIGN KEY (foreign_id) REFERENCES my_table (id) MATCH full DEFERRABLE INITIALLY DEFERRED',
Sergei Morozov's avatar
Sergei Morozov committed
140
            $this->platform->getForeignKeyDeclarationSQL($foreignKey)
141 142 143
        );
    }

144
    public function testGeneratesSqlSnippets() : void
145
    {
Sergei Morozov's avatar
Sergei Morozov committed
146 147 148
        self::assertEquals('SIMILAR TO', $this->platform->getRegexpExpression(), 'Regular expression operator is not correct');
        self::assertEquals('"', $this->platform->getIdentifierQuoteCharacter(), 'Identifier quote character is not correct');
        self::assertEquals('column1 || column2 || column3', $this->platform->getConcatExpression('column1', 'column2', 'column3'), 'Concatenation expression is not correct');
Michael Moravec's avatar
Michael Moravec committed
149 150
        self::assertEquals('SUBSTRING(column FROM 5)', $this->platform->getSubstringExpression('column', '5'), 'Substring expression without length is not correct');
        self::assertEquals('SUBSTRING(column FROM 1 FOR 5)', $this->platform->getSubstringExpression('column', '1', '5'), 'Substring expression with length is not correct');
151 152
    }

153
    public function testGeneratesTransactionCommands() : void
154
    {
155
        self::assertEquals(
156
            'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ UNCOMMITTED',
Sergei Morozov's avatar
Sergei Morozov committed
157
            $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::READ_UNCOMMITTED)
158
        );
159
        self::assertEquals(
160
            'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED',
Sergei Morozov's avatar
Sergei Morozov committed
161
            $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::READ_COMMITTED)
162
        );
163
        self::assertEquals(
164
            'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ',
Sergei Morozov's avatar
Sergei Morozov committed
165
            $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::REPEATABLE_READ)
166
        );
167
        self::assertEquals(
168
            'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE',
Sergei Morozov's avatar
Sergei Morozov committed
169
            $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::SERIALIZABLE)
170 171 172
        );
    }

173
    public function testGeneratesDDLSnippets() : void
174
    {
Sergei Morozov's avatar
Sergei Morozov committed
175 176 177
        self::assertEquals('CREATE DATABASE foobar', $this->platform->getCreateDatabaseSQL('foobar'));
        self::assertEquals('DROP DATABASE foobar', $this->platform->getDropDatabaseSQL('foobar'));
        self::assertEquals('DROP TABLE foobar', $this->platform->getDropTableSQL('foobar'));
178 179
    }

180
    public function testGenerateTableWithAutoincrement() : void
181
    {
Sergei Morozov's avatar
Sergei Morozov committed
182
        $table  = new Table('autoinc_table');
183 184 185
        $column = $table->addColumn('id', 'integer');
        $column->setAutoincrement(true);

Sergei Morozov's avatar
Sergei Morozov committed
186
        self::assertEquals(['CREATE TABLE autoinc_table (id SERIAL NOT NULL)'], $this->platform->getCreateTableSQL($table));
187 188
    }

Sergei Morozov's avatar
Sergei Morozov committed
189
    /**
190
     * @return mixed[][]
Sergei Morozov's avatar
Sergei Morozov committed
191
     */
192
    public static function serialTypes() : iterable
193 194 195 196 197 198 199 200 201 202 203 204 205
    {
        return [
            ['integer', 'SERIAL'],
            ['bigint', 'BIGSERIAL'],
        ];
    }

    /**
     * @dataProvider serialTypes
     * @group 2906
     */
    public function testGenerateTableWithAutoincrementDoesNotSetDefault(string $type, string $definition) : void
    {
Sergei Morozov's avatar
Sergei Morozov committed
206
        $table  = new Table('autoinc_table_notnull');
207
        $column = $table->addColumn('id', $type);
208 209
        $column->setAutoincrement(true);
        $column->setNotnull(false);
210

Sergei Morozov's avatar
Sergei Morozov committed
211
        $sql = $this->platform->getCreateTableSQL($table);
212

Sergei Morozov's avatar
Sergei Morozov committed
213
        self::assertEquals([sprintf('CREATE TABLE autoinc_table_notnull (id %s)', $definition)], $sql);
214 215 216 217 218 219 220 221
    }

    /**
     * @dataProvider serialTypes
     * @group 2906
     */
    public function testCreateTableWithAutoincrementAndNotNullAddsConstraint(string $type, string $definition) : void
    {
Sergei Morozov's avatar
Sergei Morozov committed
222
        $table  = new Table('autoinc_table_notnull_enabled');
223
        $column = $table->addColumn('id', $type);
224 225
        $column->setAutoincrement(true);
        $column->setNotnull(true);
226

Sergei Morozov's avatar
Sergei Morozov committed
227
        $sql = $this->platform->getCreateTableSQL($table);
228

Sergei Morozov's avatar
Sergei Morozov committed
229
        self::assertEquals([sprintf('CREATE TABLE autoinc_table_notnull_enabled (id %s NOT NULL)', $definition)], $sql);
230 231 232 233 234 235 236 237
    }

    /**
     * @dataProvider serialTypes
     * @group 2906
     */
    public function testGetDefaultValueDeclarationSQLIgnoresTheDefaultKeyWhenTheFieldIsSerial(string $type) : void
    {
Sergei Morozov's avatar
Sergei Morozov committed
238
        $sql = $this->platform->getDefaultValueDeclarationSQL(
239 240 241 242 243 244 245 246 247 248
            [
                'autoincrement' => true,
                'type'          => Type::getType($type),
                'default'       => 1,
            ]
        );

        self::assertSame('', $sql);
    }

249
    public function testGeneratesTypeDeclarationForIntegers() : void
250
    {
251
        self::assertEquals(
252
            'INT',
Sergei Morozov's avatar
Sergei Morozov committed
253
            $this->platform->getIntegerTypeDeclarationSQL([])
254
        );
255
        self::assertEquals(
256
            'SERIAL',
Sergei Morozov's avatar
Sergei Morozov committed
257
            $this->platform->getIntegerTypeDeclarationSQL(['autoincrement' => true])
Sergei Morozov's avatar
Sergei Morozov committed
258
        );
259
        self::assertEquals(
260
            'SERIAL',
Sergei Morozov's avatar
Sergei Morozov committed
261
            $this->platform->getIntegerTypeDeclarationSQL(
Sergei Morozov's avatar
Sergei Morozov committed
262 263 264
                ['autoincrement' => true, 'primary' => true]
            )
        );
265 266
    }

267
    public function getGenerateUniqueIndexSql() : string
268 269 270 271
    {
        return 'CREATE UNIQUE INDEX index_name ON test (test, test2)';
    }

272
    public function testGeneratesSequenceSqlCommands() : void
273
    {
Sergei Morozov's avatar
Sergei Morozov committed
274
        $sequence = new Sequence('myseq', 20, 1);
275
        self::assertEquals(
276
            'CREATE SEQUENCE myseq INCREMENT BY 20 MINVALUE 1 START 1',
Sergei Morozov's avatar
Sergei Morozov committed
277
            $this->platform->getCreateSequenceSQL($sequence)
278
        );
279
        self::assertEquals(
280
            'DROP SEQUENCE myseq CASCADE',
Sergei Morozov's avatar
Sergei Morozov committed
281
            $this->platform->getDropSequenceSQL('myseq')
282
        );
283
        self::assertEquals(
284
            "SELECT NEXTVAL('myseq')",
Sergei Morozov's avatar
Sergei Morozov committed
285
            $this->platform->getSequenceNextValSQL('myseq')
286 287 288
        );
    }

289
    public function testDoesNotPreferIdentityColumns() : void
290
    {
Sergei Morozov's avatar
Sergei Morozov committed
291
        self::assertFalse($this->platform->prefersIdentityColumns());
292 293
    }

294
    public function testPrefersSequences() : void
295
    {
Sergei Morozov's avatar
Sergei Morozov committed
296
        self::assertTrue($this->platform->prefersSequences());
297 298
    }

299
    public function testSupportsIdentityColumns() : void
300
    {
Sergei Morozov's avatar
Sergei Morozov committed
301
        self::assertTrue($this->platform->supportsIdentityColumns());
302 303
    }

304
    public function testSupportsSavePoints() : void
305
    {
Sergei Morozov's avatar
Sergei Morozov committed
306
        self::assertTrue($this->platform->supportsSavepoints());
307 308
    }

309
    public function testSupportsSequences() : void
310
    {
Sergei Morozov's avatar
Sergei Morozov committed
311
        self::assertTrue($this->platform->supportsSequences());
312 313
    }

314
    protected function supportsCommentOnStatement() : bool
315 316 317 318
    {
        return true;
    }

319
    public function testModifyLimitQuery() : void
320
    {
Sergei Morozov's avatar
Sergei Morozov committed
321
        $sql = $this->platform->modifyLimitQuery('SELECT * FROM user', 10, 0);
322
        self::assertEquals('SELECT * FROM user LIMIT 10', $sql);
323 324
    }

325
    public function testModifyLimitQueryWithEmptyOffset() : void
326
    {
Sergei Morozov's avatar
Sergei Morozov committed
327
        $sql = $this->platform->modifyLimitQuery('SELECT * FROM user', 10);
328
        self::assertEquals('SELECT * FROM user LIMIT 10', $sql);
329 330
    }

331 332 333 334
    /**
     * {@inheritDoc}
     */
    public function getCreateTableColumnCommentsSQL() : array
335
    {
Sergei Morozov's avatar
Sergei Morozov committed
336 337
        return [
            'CREATE TABLE test (id INT NOT NULL, PRIMARY KEY(id))',
338
            "COMMENT ON COLUMN test.id IS 'This is a comment'",
Sergei Morozov's avatar
Sergei Morozov committed
339
        ];
340 341
    }

342 343 344 345
    /**
     * {@inheritDoc}
     */
    public function getAlterTableColumnCommentsSQL() : array
346
    {
Sergei Morozov's avatar
Sergei Morozov committed
347 348
        return [
            'ALTER TABLE mytable ADD quota INT NOT NULL',
349
            "COMMENT ON COLUMN mytable.quota IS 'A comment'",
350
            "COMMENT ON COLUMN mytable.foo IS ''",
351
            "COMMENT ON COLUMN mytable.baz IS 'B comment'",
Sergei Morozov's avatar
Sergei Morozov committed
352
        ];
353 354
    }

355 356 357 358
    /**
     * {@inheritDoc}
     */
    public function getCreateTableColumnTypeCommentsSQL() : array
359
    {
Sergei Morozov's avatar
Sergei Morozov committed
360 361 362 363
        return [
            'CREATE TABLE test (id INT NOT NULL, data TEXT NOT NULL, PRIMARY KEY(id))',
            "COMMENT ON COLUMN test.data IS '(DC2Type:array)'",
        ];
364 365
    }

366 367 368 369
    /**
     * {@inheritDoc}
     */
    protected function getQuotedColumnInPrimaryKeySQL() : array
370
    {
Sergei Morozov's avatar
Sergei Morozov committed
371
        return ['CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL, PRIMARY KEY("create"))'];
372 373
    }

374 375 376 377
    /**
     * {@inheritDoc}
     */
    protected function getQuotedColumnInIndexSQL() : array
378
    {
Sergei Morozov's avatar
Sergei Morozov committed
379
        return [
380 381
            'CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL)',
            'CREATE INDEX IDX_22660D028FD6E0FB ON "quoted" ("create")',
Sergei Morozov's avatar
Sergei Morozov committed
382
        ];
383 384
    }

385 386 387 388
    /**
     * {@inheritDoc}
     */
    protected function getQuotedNameInIndexSQL() : array
Markus Fasselt's avatar
Markus Fasselt committed
389
    {
Sergei Morozov's avatar
Sergei Morozov committed
390
        return [
Markus Fasselt's avatar
Markus Fasselt committed
391 392
            'CREATE TABLE test (column1 VARCHAR(255) NOT NULL)',
            'CREATE INDEX "key" ON test (column1)',
Sergei Morozov's avatar
Sergei Morozov committed
393
        ];
Markus Fasselt's avatar
Markus Fasselt committed
394 395
    }

396 397 398 399
    /**
     * {@inheritDoc}
     */
    protected function getQuotedColumnInForeignKeySQL() : array
400
    {
Sergei Morozov's avatar
Sergei Morozov committed
401
        return [
402 403 404 405
            'CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL, foo VARCHAR(255) NOT NULL, "bar" VARCHAR(255) NOT NULL)',
            'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_RESERVED_KEYWORD FOREIGN KEY ("create", foo, "bar") REFERENCES "foreign" ("create", bar, "foo-bar") NOT DEFERRABLE INITIALLY IMMEDIATE',
            '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',
            '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',
Sergei Morozov's avatar
Sergei Morozov committed
406
        ];
407 408 409
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
410 411
     * @group DBAL-457
     * @dataProvider pgBooleanProvider
412
     */
413
    public function testConvertBooleanAsLiteralStrings(
414
        string $databaseValue,
415
        string $preparedStatementValue,
416 417
        int $integerValue,
        bool $booleanValue
418
    ) : void {
419
        self::assertEquals($preparedStatementValue, $this->platform->convertBooleans($databaseValue));
420 421 422 423 424
    }

    /**
     * @group DBAL-457
     */
425
    public function testConvertBooleanAsLiteralIntegers() : void
426
    {
427
        $this->platform->setUseBooleanTrueFalseStrings(false);
428

429 430
        self::assertEquals(1, $this->platform->convertBooleans(true));
        self::assertEquals(1, $this->platform->convertBooleans('1'));
431

432 433
        self::assertEquals(0, $this->platform->convertBooleans(false));
        self::assertEquals(0, $this->platform->convertBooleans('0'));
434 435 436
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
437 438
     * @group DBAL-630
     * @dataProvider pgBooleanProvider
439
     */
440
    public function testConvertBooleanAsDatabaseValueStrings(
441
        string $databaseValue,
442
        string $preparedStatementValue,
443 444
        int $integerValue,
        bool $booleanValue
445
    ) : void {
446
        self::assertSame($integerValue, $this->platform->convertBooleansToDatabaseValue($booleanValue));
447 448 449 450 451
    }

    /**
     * @group DBAL-630
     */
452
    public function testConvertBooleanAsDatabaseValueIntegers() : void
453
    {
454
        $this->platform->setUseBooleanTrueFalseStrings(false);
455

456 457
        self::assertSame(1, $this->platform->convertBooleansToDatabaseValue(true));
        self::assertSame(0, $this->platform->convertBooleansToDatabaseValue(false));
458
    }
459 460

    /**
Sergei Morozov's avatar
Sergei Morozov committed
461
     * @dataProvider pgBooleanProvider
462
     */
463
    public function testConvertFromBoolean(string $databaseValue, string $prepareStatementValue, int $integerValue, bool $booleanValue) : void
464
    {
465
        self::assertSame($booleanValue, $this->platform->convertFromBoolean($databaseValue));
466
    }
467

468
    public function testThrowsExceptionWithInvalidBooleanLiteral() : void
469
    {
470
        $this->expectException(UnexpectedValueException::class);
471
        $this->expectExceptionMessage('Unrecognized boolean literal, my-bool given.');
472

473
        $this->platform->convertBooleansToDatabaseValue('my-bool');
474 475
    }

476
    public function testGetCreateSchemaSQL() : void
477 478
    {
        $schemaName = 'schema';
Sergei Morozov's avatar
Sergei Morozov committed
479
        $sql        = $this->platform->getCreateSchemaSQL($schemaName);
480
        self::assertEquals('CREATE SCHEMA ' . $schemaName, $sql);
481 482
    }

483
    public function testAlterDecimalPrecisionScale() : void
484 485 486
    {
        $table = new Table('mytable');
        $table->addColumn('dfoo1', 'decimal');
Sergei Morozov's avatar
Sergei Morozov committed
487 488 489
        $table->addColumn('dfoo2', 'decimal', ['precision' => 10, 'scale' => 6]);
        $table->addColumn('dfoo3', 'decimal', ['precision' => 10, 'scale' => 6]);
        $table->addColumn('dfoo4', 'decimal', ['precision' => 10, 'scale' => 6]);
490

Sergei Morozov's avatar
Sergei Morozov committed
491
        $tableDiff            = new TableDiff('mytable');
492 493
        $tableDiff->fromTable = $table;

Sergei Morozov's avatar
Sergei Morozov committed
494 495 496 497 498 499
        $tableDiff->changedColumns['dloo1'] = new ColumnDiff(
            'dloo1',
            new Column(
                'dloo1',
                Type::getType('decimal'),
                ['precision' => 16, 'scale' => 6]
500
            ),
Sergei Morozov's avatar
Sergei Morozov committed
501 502 503 504 505 506 507 508
            ['precision']
        );
        $tableDiff->changedColumns['dloo2'] = new ColumnDiff(
            'dloo2',
            new Column(
                'dloo2',
                Type::getType('decimal'),
                ['precision' => 10, 'scale' => 4]
509
            ),
Sergei Morozov's avatar
Sergei Morozov committed
510 511 512 513 514 515 516 517
            ['scale']
        );
        $tableDiff->changedColumns['dloo3'] = new ColumnDiff(
            'dloo3',
            new Column(
                'dloo3',
                Type::getType('decimal'),
                ['precision' => 10, 'scale' => 6]
518
            ),
Sergei Morozov's avatar
Sergei Morozov committed
519 520 521 522 523 524 525 526
            []
        );
        $tableDiff->changedColumns['dloo4'] = new ColumnDiff(
            'dloo4',
            new Column(
                'dloo4',
                Type::getType('decimal'),
                ['precision' => 16, 'scale' => 8]
527
            ),
Sergei Morozov's avatar
Sergei Morozov committed
528
            ['precision', 'scale']
529 530
        );

Sergei Morozov's avatar
Sergei Morozov committed
531
        $sql = $this->platform->getAlterTableSQL($tableDiff);
532

Sergei Morozov's avatar
Sergei Morozov committed
533
        $expectedSql = [
534 535 536
            'ALTER TABLE mytable ALTER dloo1 TYPE NUMERIC(16, 6)',
            'ALTER TABLE mytable ALTER dloo2 TYPE NUMERIC(10, 4)',
            'ALTER TABLE mytable ALTER dloo4 TYPE NUMERIC(16, 8)',
Sergei Morozov's avatar
Sergei Morozov committed
537
        ];
538

539
        self::assertEquals($expectedSql, $sql);
540 541 542 543 544
    }

    /**
     * @group DBAL-365
     */
545
    public function testDroppingConstraintsBeforeColumns() : void
546 547 548
    {
        $newTable = new Table('mytable');
        $newTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
549
        $newTable->setPrimaryKey(['id']);
550 551 552

        $oldTable = clone $newTable;
        $oldTable->addColumn('parent_id', 'integer');
553
        $oldTable->addForeignKeyConstraint('mytable', ['parent_id'], ['id']);
554

Sergei Morozov's avatar
Sergei Morozov committed
555 556
        $comparator = new Comparator();
        $tableDiff  = $comparator->diffTable($oldTable, $newTable);
557

558 559
        self::assertNotNull($tableDiff);

Sergei Morozov's avatar
Sergei Morozov committed
560
        $sql = $this->platform->getAlterTableSQL($tableDiff);
561

Sergei Morozov's avatar
Sergei Morozov committed
562
        $expectedSql = [
563 564 565
            'ALTER TABLE mytable DROP CONSTRAINT FK_6B2BD609727ACA70',
            'DROP INDEX IDX_6B2BD609727ACA70',
            'ALTER TABLE mytable DROP parent_id',
Sergei Morozov's avatar
Sergei Morozov committed
566
        ];
567

568
        self::assertEquals($expectedSql, $sql);
569 570 571 572 573
    }

    /**
     * @group DBAL-563
     */
574
    public function testUsesSequenceEmulatedIdentityColumns() : void
575
    {
Sergei Morozov's avatar
Sergei Morozov committed
576
        self::assertTrue($this->platform->usesSequenceEmulatedIdentityColumns());
577 578 579 580 581
    }

    /**
     * @group DBAL-563
     */
582
    public function testReturnsIdentitySequenceName() : void
583
    {
Sergei Morozov's avatar
Sergei Morozov committed
584
        self::assertSame('mytable_mycolumn_seq', $this->platform->getIdentitySequenceName('mytable', 'mycolumn'));
585 586 587 588 589 590
    }

    /**
     * @dataProvider dataCreateSequenceWithCache
     * @group DBAL-139
     */
591
    public function testCreateSequenceWithCache(int $cacheSize, string $expectedSql) : void
592
    {
Sergei Morozov's avatar
Sergei Morozov committed
593
        $sequence = new Sequence('foo', 1, 1, $cacheSize);
594
        self::assertStringContainsString($expectedSql, $this->platform->getCreateSequenceSQL($sequence));
595 596
    }

597 598 599 600
    /**
     * @return mixed[][]
     */
    public static function dataCreateSequenceWithCache() : iterable
601
    {
Sergei Morozov's avatar
Sergei Morozov committed
602 603 604
        return [
            [3, 'CACHE 3'],
        ];
605 606
    }

607
    public function getExpectedFixedLengthBinaryTypeDeclarationSQLNoLength() : string
608
    {
609
        return 'BYTEA';
610 611
    }

612
    public function getExpectedFixedLengthBinaryTypeDeclarationSQLWithLength() : string
613
    {
614
        return 'BYTEA';
615 616
    }

617
    public function getExpectedVariableLengthBinaryTypeDeclarationSQLNoLength() : string
618
    {
619 620
        return 'BYTEA';
    }
621

622 623 624
    public function getExpectedVariableLengthBinaryTypeDeclarationSQLWithLength() : string
    {
        return 'BYTEA';
625 626
    }

627
    public function testDoesNotPropagateUnnecessaryTableAlterationOnBinaryType() : void
628 629 630
    {
        $table1 = new Table('mytable');
        $table1->addColumn('column_varbinary', 'binary');
Sergei Morozov's avatar
Sergei Morozov committed
631
        $table1->addColumn('column_binary', 'binary', ['fixed' => true]);
632 633 634
        $table1->addColumn('column_blob', 'blob');

        $table2 = new Table('mytable');
Sergei Morozov's avatar
Sergei Morozov committed
635
        $table2->addColumn('column_varbinary', 'binary', ['fixed' => true]);
636 637 638 639 640
        $table2->addColumn('column_binary', 'binary');
        $table2->addColumn('column_blob', 'binary');

        $comparator = new Comparator();

641 642 643 644
        $diff = $comparator->diffTable($table1, $table2);

        self::assertNotNull($diff);

645 646 647
        // VARBINARY -> BINARY
        // BINARY    -> VARBINARY
        // BLOB      -> VARBINARY
648
        self::assertEmpty($this->platform->getAlterTableSQL($diff));
649 650

        $table2 = new Table('mytable');
Sergei Morozov's avatar
Sergei Morozov committed
651
        $table2->addColumn('column_varbinary', 'binary', ['length' => 42]);
652
        $table2->addColumn('column_binary', 'blob');
Sergei Morozov's avatar
Sergei Morozov committed
653
        $table2->addColumn('column_blob', 'binary', ['length' => 11, 'fixed' => true]);
654

655 656 657 658
        $diff = $comparator->diffTable($table1, $table2);

        self::assertNotNull($diff);

659 660 661
        // VARBINARY -> VARBINARY with changed length
        // BINARY    -> BLOB
        // BLOB      -> BINARY
662
        self::assertEmpty($this->platform->getAlterTableSQL($diff));
663 664 665

        $table2 = new Table('mytable');
        $table2->addColumn('column_varbinary', 'blob');
Sergei Morozov's avatar
Sergei Morozov committed
666
        $table2->addColumn('column_binary', 'binary', ['length' => 42, 'fixed' => true]);
667 668
        $table2->addColumn('column_blob', 'blob');

669 670 671 672
        $diff = $comparator->diffTable($table1, $table2);

        self::assertNotNull($diff);

673 674 675
        // VARBINARY -> BLOB
        // BINARY    -> BINARY with changed length
        // BLOB      -> BLOB
676
        self::assertEmpty($this->platform->getAlterTableSQL($diff));
677 678 679
    }

    /**
680 681
     * {@inheritDoc}
     *
682 683
     * @group DBAL-234
     */
684
    protected function getAlterTableRenameIndexSQL() : array
685
    {
Sergei Morozov's avatar
Sergei Morozov committed
686
        return ['ALTER INDEX idx_foo RENAME TO idx_bar'];
687 688 689
    }

    /**
690 691
     * {@inheritDoc}
     *
692 693
     * @group DBAL-234
     */
694
    protected function getQuotedAlterTableRenameIndexSQL() : array
695
    {
Sergei Morozov's avatar
Sergei Morozov committed
696
        return [
697 698
            'ALTER INDEX "create" RENAME TO "select"',
            'ALTER INDEX "foo" RENAME TO "bar"',
Sergei Morozov's avatar
Sergei Morozov committed
699
        ];
700
    }
701

702 703
    /**
     * PostgreSQL boolean strings provider
Sergei Morozov's avatar
Sergei Morozov committed
704
     *
705
     * @return mixed[][]
706
     */
707
    public static function pgBooleanProvider() : iterable
708
    {
Sergei Morozov's avatar
Sergei Morozov committed
709
        return [
710
            // Database value, prepared statement value, boolean integer value, boolean value.
Sergei Morozov's avatar
Sergei Morozov committed
711 712 713 714 715 716 717 718 719 720 721 722 723 724
            ['t', 'true', 1, true],
            ['true', 'true', 1, true],
            ['y', 'true', 1, true],
            ['yes', 'true', 1, true],
            ['on', 'true', 1, true],
            ['1', 'true', 1, true],

            ['f', 'false', 0, false],
            ['false', 'false', 0, false],
            [ 'n', 'false', 0, false],
            ['no', 'false', 0, false],
            ['off', 'false', 0, false],
            ['0', 'false', 0, false],
        ];
725 726
    }

727 728 729
    /**
     * {@inheritdoc}
     */
730
    protected function getQuotedAlterTableRenameColumnSQL() : array
731
    {
Sergei Morozov's avatar
Sergei Morozov committed
732
        return [
733 734 735 736 737 738 739 740 741
            'ALTER TABLE mytable RENAME COLUMN unquoted1 TO unquoted',
            'ALTER TABLE mytable RENAME COLUMN unquoted2 TO "where"',
            'ALTER TABLE mytable RENAME COLUMN unquoted3 TO "foo"',
            'ALTER TABLE mytable RENAME COLUMN "create" TO reserved_keyword',
            'ALTER TABLE mytable RENAME COLUMN "table" TO "from"',
            'ALTER TABLE mytable RENAME COLUMN "select" TO "bar"',
            'ALTER TABLE mytable RENAME COLUMN quoted1 TO quoted',
            'ALTER TABLE mytable RENAME COLUMN quoted2 TO "and"',
            'ALTER TABLE mytable RENAME COLUMN quoted3 TO "baz"',
Sergei Morozov's avatar
Sergei Morozov committed
742
        ];
743
    }
744

745 746 747
    /**
     * {@inheritdoc}
     */
748
    protected function getQuotedAlterTableChangeColumnLengthSQL() : array
749
    {
Sergei Morozov's avatar
Sergei Morozov committed
750
        return [
751 752 753 754 755 756
            'ALTER TABLE mytable ALTER unquoted1 TYPE VARCHAR(255)',
            'ALTER TABLE mytable ALTER unquoted2 TYPE VARCHAR(255)',
            'ALTER TABLE mytable ALTER unquoted3 TYPE VARCHAR(255)',
            'ALTER TABLE mytable ALTER "create" TYPE VARCHAR(255)',
            'ALTER TABLE mytable ALTER "table" TYPE VARCHAR(255)',
            'ALTER TABLE mytable ALTER "select" TYPE VARCHAR(255)',
Sergei Morozov's avatar
Sergei Morozov committed
757
        ];
758 759
    }

760
    /**
761 762
     * {@inheritDoc}
     *
763 764
     * @group DBAL-807
     */
765
    protected function getAlterTableRenameIndexInSchemaSQL() : array
766
    {
Sergei Morozov's avatar
Sergei Morozov committed
767
        return ['ALTER INDEX myschema.idx_foo RENAME TO idx_bar'];
768 769 770
    }

    /**
771 772
     * {@inheritDoc}
     *
773 774
     * @group DBAL-807
     */
775
    protected function getQuotedAlterTableRenameIndexInSchemaSQL() : array
776
    {
Sergei Morozov's avatar
Sergei Morozov committed
777
        return [
778 779
            'ALTER INDEX "schema"."create" RENAME TO "select"',
            'ALTER INDEX "schema"."foo" RENAME TO "bar"',
Sergei Morozov's avatar
Sergei Morozov committed
780
        ];
781
    }
782

783
    protected function getQuotesDropForeignKeySQL() : string
784 785 786 787
    {
        return 'ALTER TABLE "table" DROP CONSTRAINT "select"';
    }

788 789 790
    /**
     * @group DBAL-423
     */
791
    public function testReturnsGuidTypeDeclarationSQL() : void
792
    {
Sergei Morozov's avatar
Sergei Morozov committed
793
        self::assertSame('UUID', $this->platform->getGuidTypeDeclarationSQL([]));
794
    }
795 796 797 798

    /**
     * {@inheritdoc}
     */
799
    public function getAlterTableRenameColumnSQL() : array
800
    {
Sergei Morozov's avatar
Sergei Morozov committed
801
        return ['ALTER TABLE foo RENAME COLUMN bar TO baz'];
802
    }
803 804 805 806

    /**
     * {@inheritdoc}
     */
807
    protected function getQuotesTableIdentifiersInAlterTableSQL() : array
808
    {
Sergei Morozov's avatar
Sergei Morozov committed
809
        return [
810 811 812 813 814 815 816 817 818 819 820
            'ALTER TABLE "foo" DROP CONSTRAINT fk1',
            'ALTER TABLE "foo" DROP CONSTRAINT fk2',
            'ALTER TABLE "foo" ADD bloo INT NOT NULL',
            'ALTER TABLE "foo" DROP baz',
            'ALTER TABLE "foo" ALTER bar DROP NOT NULL',
            'ALTER TABLE "foo" RENAME COLUMN id TO war',
            'ALTER TABLE "foo" RENAME TO "table"',
            'ALTER TABLE "table" ADD CONSTRAINT fk_add FOREIGN KEY (fk3) REFERENCES fk_table (id) NOT DEFERRABLE ' .
            'INITIALLY IMMEDIATE',
            'ALTER TABLE "table" ADD CONSTRAINT fk2 FOREIGN KEY (fk2) REFERENCES fk_table2 (id) NOT DEFERRABLE ' .
            'INITIALLY IMMEDIATE',
Sergei Morozov's avatar
Sergei Morozov committed
821
        ];
822
    }
823 824 825 826

    /**
     * {@inheritdoc}
     */
827
    protected function getCommentOnColumnSQL() : array
828
    {
Sergei Morozov's avatar
Sergei Morozov committed
829
        return [
830 831 832
            'COMMENT ON COLUMN foo.bar IS \'comment\'',
            'COMMENT ON COLUMN "Foo"."BAR" IS \'comment\'',
            'COMMENT ON COLUMN "select"."from" IS \'comment\'',
Sergei Morozov's avatar
Sergei Morozov committed
833
        ];
834 835 836 837 838
    }

    /**
     * @group DBAL-1004
     */
839
    public function testAltersTableColumnCommentWithExplicitlyQuotedIdentifiers() : void
840
    {
Sergei Morozov's avatar
Sergei Morozov committed
841 842
        $table1 = new Table('"foo"', [new Column('"bar"', Type::getType('integer'))]);
        $table2 = new Table('"foo"', [new Column('"bar"', Type::getType('integer'), ['comment' => 'baz'])]);
843 844 845 846 847

        $comparator = new Comparator();

        $tableDiff = $comparator->diffTable($table1, $table2);

Sergei Morozov's avatar
Sergei Morozov committed
848
        self::assertInstanceOf(TableDiff::class, $tableDiff);
849
        self::assertSame(
Sergei Morozov's avatar
Sergei Morozov committed
850
            ['COMMENT ON COLUMN "foo"."bar" IS \'baz\''],
Sergei Morozov's avatar
Sergei Morozov committed
851
            $this->platform->getAlterTableSQL($tableDiff)
852 853
        );
    }
854

855 856 857
    /**
     * @group 3158
     */
858
    public function testAltersTableColumnCommentIfRequiredByType() : void
859 860 861 862 863 864 865 866
    {
        $table1 = new Table('"foo"', [new Column('"bar"', Type::getType('datetime'))]);
        $table2 = new Table('"foo"', [new Column('"bar"', Type::getType('datetime_immutable'))]);

        $comparator = new Comparator();

        $tableDiff = $comparator->diffTable($table1, $table2);

867 868
        self::assertInstanceOf('Doctrine\DBAL\Schema\TableDiff', $tableDiff);
        self::assertSame(
869 870 871 872 873 874 875 876 877
            [
                'ALTER TABLE "foo" ALTER "bar" TYPE TIMESTAMP(0) WITHOUT TIME ZONE',
                'ALTER TABLE "foo" ALTER "bar" DROP DEFAULT',
                'COMMENT ON COLUMN "foo"."bar" IS \'(DC2Type:datetime_immutable)\'',
            ],
            $this->platform->getAlterTableSQL($tableDiff)
        );
    }

878
    protected function getQuotesReservedKeywordInUniqueConstraintDeclarationSQL() : string
879 880 881 882
    {
        return 'CONSTRAINT "select" UNIQUE (foo)';
    }

883
    protected function getQuotesReservedKeywordInIndexDeclarationSQL() : string
884 885 886
    {
        return 'INDEX "select" (foo)';
    }
887

888
    protected function getQuotesReservedKeywordInTruncateTableSQL() : string
889 890 891 892
    {
        return 'TRUNCATE "select"';
    }

893
    /**
894
     * {@inheritdoc}
895
     */
896
    protected function getAlterStringToFixedStringSQL() : array
897
    {
Sergei Morozov's avatar
Sergei Morozov committed
898
        return ['ALTER TABLE mytable ALTER name TYPE CHAR(2)'];
899
    }
900 901 902 903

    /**
     * {@inheritdoc}
     */
904
    protected function getGeneratesAlterTableRenameIndexUsedByForeignKeySQL() : array
905
    {
Sergei Morozov's avatar
Sergei Morozov committed
906
        return ['ALTER INDEX idx_foo RENAME TO idx_foo_renamed'];
907
    }
Steve Müller's avatar
Steve Müller committed
908 909 910 911

    /**
     * @group DBAL-1142
     */
912
    public function testInitializesTsvectorTypeMapping() : void
Steve Müller's avatar
Steve Müller committed
913
    {
Sergei Morozov's avatar
Sergei Morozov committed
914 915
        self::assertTrue($this->platform->hasDoctrineTypeMappingFor('tsvector'));
        self::assertEquals('text', $this->platform->getDoctrineTypeMapping('tsvector'));
Steve Müller's avatar
Steve Müller committed
916
    }
917 918 919 920

    /**
     * @group DBAL-1220
     */
921
    public function testReturnsDisallowDatabaseConnectionsSQL() : void
922
    {
923
        self::assertSame(
924
            "UPDATE pg_database SET datallowconn = 'false' WHERE datname = 'foo'",
Sergei Morozov's avatar
Sergei Morozov committed
925
            $this->platform->getDisallowDatabaseConnectionsSQL('foo')
926 927 928 929 930 931
        );
    }

    /**
     * @group DBAL-1220
     */
932
    public function testReturnsCloseActiveDatabaseConnectionsSQL() : void
933
    {
934
        self::assertSame(
935
            "SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname = 'foo'",
Sergei Morozov's avatar
Sergei Morozov committed
936
            $this->platform->getCloseActiveDatabaseConnectionsSQL('foo')
937 938
        );
    }
939 940 941 942

    /**
     * @group DBAL-2436
     */
943
    public function testQuotesTableNameInListTableForeignKeysSQL() : void
944
    {
945 946 947 948
        self::assertStringContainsStringIgnoringCase(
            "'Foo''Bar\\'",
            $this->platform->getListTableForeignKeysSQL("Foo'Bar\\")
        );
949 950 951 952 953
    }

    /**
     * @group DBAL-2436
     */
954
    public function testQuotesSchemaNameInListTableForeignKeysSQL() : void
955
    {
956
        self::assertStringContainsStringIgnoringCase(
957
            "'Foo''Bar\\'",
958
            $this->platform->getListTableForeignKeysSQL("Foo'Bar\\.baz_table")
959 960 961 962 963 964
        );
    }

    /**
     * @group DBAL-2436
     */
965
    public function testQuotesTableNameInListTableConstraintsSQL() : void
966
    {
967 968 969 970
        self::assertStringContainsStringIgnoringCase(
            "'Foo''Bar\\'",
            $this->platform->getListTableConstraintsSQL("Foo'Bar\\")
        );
971 972 973 974 975
    }

    /**
     * @group DBAL-2436
     */
976
    public function testQuotesTableNameInListTableIndexesSQL() : void
977
    {
978 979 980 981
        self::assertStringContainsStringIgnoringCase(
            "'Foo''Bar\\'",
            $this->platform->getListTableIndexesSQL("Foo'Bar\\")
        );
982 983 984 985 986
    }

    /**
     * @group DBAL-2436
     */
987
    public function testQuotesSchemaNameInListTableIndexesSQL() : void
988
    {
989
        self::assertStringContainsStringIgnoringCase(
990
            "'Foo''Bar\\'",
991
            $this->platform->getListTableIndexesSQL("Foo'Bar\\.baz_table")
992 993 994 995 996 997
        );
    }

    /**
     * @group DBAL-2436
     */
998
    public function testQuotesTableNameInListTableColumnsSQL() : void
999
    {
1000 1001 1002 1003
        self::assertStringContainsStringIgnoringCase(
            "'Foo''Bar\\'",
            $this->platform->getListTableColumnsSQL("Foo'Bar\\")
        );
1004 1005 1006 1007 1008
    }

    /**
     * @group DBAL-2436
     */
1009
    public function testQuotesSchemaNameInListTableColumnsSQL() : void
1010
    {
1011
        self::assertStringContainsStringIgnoringCase(
1012
            "'Foo''Bar\\'",
1013
            $this->platform->getListTableColumnsSQL("Foo'Bar\\.baz_table")
1014 1015 1016 1017 1018 1019
        );
    }

    /**
     * @group DBAL-2436
     */
1020
    public function testQuotesDatabaseNameInCloseActiveDatabaseConnectionsSQL() : void
1021
    {
1022
        self::assertStringContainsStringIgnoringCase(
1023
            "'Foo''Bar\\'",
1024
            $this->platform->getCloseActiveDatabaseConnectionsSQL("Foo'Bar\\")
1025 1026
        );
    }
1027
}