AbstractPlatformTestCase.php 51.2 KB
Newer Older
1 2
<?php

3
namespace Doctrine\DBAL\Tests\Platforms;
4

5
use Doctrine\Common\EventManager;
Sergei Morozov's avatar
Sergei Morozov committed
6
use Doctrine\DBAL\DBALException;
7
use Doctrine\DBAL\Events;
8
use Doctrine\DBAL\Platforms\AbstractPlatform;
Sergei Morozov's avatar
Sergei Morozov committed
9
use Doctrine\DBAL\Platforms\Keywords\KeywordList;
10
use Doctrine\DBAL\Schema\Column;
11
use Doctrine\DBAL\Schema\ColumnDiff;
12
use Doctrine\DBAL\Schema\Comparator;
13
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
14
use Doctrine\DBAL\Schema\Index;
15
use Doctrine\DBAL\Schema\Table;
16
use Doctrine\DBAL\Schema\TableDiff;
17
use Doctrine\DBAL\Schema\UniqueConstraint;
18
use Doctrine\DBAL\Types\Type;
19
use PHPUnit\Framework\TestCase;
20 21 22 23
use function get_class;
use function implode;
use function sprintf;
use function str_repeat;
24

25
abstract class AbstractPlatformTestCase extends TestCase
26
{
Sergei Morozov's avatar
Sergei Morozov committed
27
    /** @var AbstractPlatform */
Sergei Morozov's avatar
Sergei Morozov committed
28
    protected $platform;
29

30
    abstract public function createPlatform() : AbstractPlatform;
31

32
    protected function setUp() : void
33
    {
Sergei Morozov's avatar
Sergei Morozov committed
34
        $this->platform = $this->createPlatform();
35 36
    }

37 38 39
    /**
     * @group DDC-1360
     */
40
    public function testQuoteIdentifier() : void
41
    {
Sergei Morozov's avatar
Sergei Morozov committed
42
        if ($this->platform->getName() === 'mssql') {
43
            self::markTestSkipped('Not working this way on mssql.');
44 45
        }

Sergei Morozov's avatar
Sergei Morozov committed
46 47 48 49
        $c = $this->platform->getIdentifierQuoteCharacter();
        self::assertEquals($c . 'test' . $c, $this->platform->quoteIdentifier('test'));
        self::assertEquals($c . 'test' . $c . '.' . $c . 'test' . $c, $this->platform->quoteIdentifier('test.test'));
        self::assertEquals(str_repeat($c, 4), $this->platform->quoteIdentifier($c));
50 51
    }

52 53 54
    /**
     * @group DDC-1360
     */
55
    public function testQuoteSingleIdentifier() : void
56
    {
Sergei Morozov's avatar
Sergei Morozov committed
57
        if ($this->platform->getName() === 'mssql') {
58
            self::markTestSkipped('Not working this way on mssql.');
59 60
        }

Sergei Morozov's avatar
Sergei Morozov committed
61 62 63 64
        $c = $this->platform->getIdentifierQuoteCharacter();
        self::assertEquals($c . 'test' . $c, $this->platform->quoteSingleIdentifier('test'));
        self::assertEquals($c . 'test.test' . $c, $this->platform->quoteSingleIdentifier('test.test'));
        self::assertEquals(str_repeat($c, 4), $this->platform->quoteSingleIdentifier($c));
65 66
    }

67 68 69 70
    /**
     * @group DBAL-1029
     * @dataProvider getReturnsForeignKeyReferentialActionSQL
     */
71
    public function testReturnsForeignKeyReferentialActionSQL(string $action, string $expectedSQL) : void
72
    {
Sergei Morozov's avatar
Sergei Morozov committed
73
        self::assertSame($expectedSQL, $this->platform->getForeignKeyReferentialActionSQL($action));
74 75 76
    }

    /**
77
     * @return mixed[][]
78
     */
79
    public static function getReturnsForeignKeyReferentialActionSQL() : iterable
80
    {
Sergei Morozov's avatar
Sergei Morozov committed
81 82 83 84 85 86 87 88
        return [
            ['CASCADE', 'CASCADE'],
            ['SET NULL', 'SET NULL'],
            ['NO ACTION', 'NO ACTION'],
            ['RESTRICT', 'RESTRICT'],
            ['SET DEFAULT', 'SET DEFAULT'],
            ['CaScAdE', 'CASCADE'],
        ];
89 90
    }

91
    public function testGetInvalidForeignKeyReferentialActionSQL() : void
92
    {
Luís Cobucci's avatar
Luís Cobucci committed
93
        $this->expectException('InvalidArgumentException');
Sergei Morozov's avatar
Sergei Morozov committed
94
        $this->platform->getForeignKeyReferentialActionSQL('unknown');
95 96
    }

97
    public function testGetUnknownDoctrineMappingType() : void
98
    {
Sergei Morozov's avatar
Sergei Morozov committed
99 100
        $this->expectException(DBALException::class);
        $this->platform->getDoctrineTypeMapping('foobar');
101 102
    }

103
    public function testRegisterDoctrineMappingType() : void
104
    {
Sergei Morozov's avatar
Sergei Morozov committed
105 106
        $this->platform->registerDoctrineTypeMapping('foo', 'integer');
        self::assertEquals('integer', $this->platform->getDoctrineTypeMapping('foo'));
107 108
    }

109
    public function testRegisterUnknownDoctrineMappingType() : void
110
    {
Sergei Morozov's avatar
Sergei Morozov committed
111 112
        $this->expectException(DBALException::class);
        $this->platform->registerDoctrineTypeMapping('foo', 'bar');
113 114
    }

115 116 117
    /**
     * @group DBAL-2594
     */
118
    public function testRegistersCommentedDoctrineMappingTypeImplicitly() : void
119
    {
120 121
        $type = Type::getType('array');
        $this->platform->registerDoctrineTypeMapping('foo', 'array');
122

Sergei Morozov's avatar
Sergei Morozov committed
123
        self::assertTrue($this->platform->isCommentedDoctrineType($type));
124 125
    }

126 127 128 129
    /**
     * @group DBAL-939
     * @dataProvider getIsCommentedDoctrineType
     */
130
    public function testIsCommentedDoctrineType(Type $type, bool $commented) : void
131
    {
Sergei Morozov's avatar
Sergei Morozov committed
132
        self::assertSame($commented, $this->platform->isCommentedDoctrineType($type));
133 134
    }

135 136 137 138
    /**
     * @return mixed[]
     */
    public function getIsCommentedDoctrineType() : iterable
139 140 141
    {
        $this->setUp();

Sergei Morozov's avatar
Sergei Morozov committed
142
        $data = [];
143 144 145 146

        foreach (Type::getTypesMap() as $typeName => $className) {
            $type = Type::getType($typeName);

Sergei Morozov's avatar
Sergei Morozov committed
147
            $data[$typeName] = [
148
                $type,
Sergei Morozov's avatar
Sergei Morozov committed
149
                $type->requiresSQLCommentHint($this->platform),
Sergei Morozov's avatar
Sergei Morozov committed
150
            ];
151 152 153 154 155
        }

        return $data;
    }

156
    public function testCreateWithNoColumns() : void
157
    {
158
        $table = new Table('test');
159

Sergei Morozov's avatar
Sergei Morozov committed
160 161
        $this->expectException(DBALException::class);
        $sql = $this->platform->getCreateTableSQL($table);
162 163
    }

164
    public function testGeneratesTableCreationSql() : void
165
    {
166
        $table = new Table('test');
Sergei Morozov's avatar
Sergei Morozov committed
167 168 169
        $table->addColumn('id', 'integer', ['notnull' => true, 'autoincrement' => true]);
        $table->addColumn('test', 'string', ['notnull' => false, 'length' => 255]);
        $table->setPrimaryKey(['id']);
170

Sergei Morozov's avatar
Sergei Morozov committed
171
        $sql = $this->platform->getCreateTableSQL($table);
172
        self::assertEquals($this->getGenerateTableSql(), $sql[0]);
173 174
    }

175
    abstract public function getGenerateTableSql() : string;
176

177
    public function testGenerateTableWithMultiColumnUniqueIndex() : void
178
    {
179
        $table = new Table('test');
Sergei Morozov's avatar
Sergei Morozov committed
180 181 182
        $table->addColumn('foo', 'string', ['notnull' => false, 'length' => 255]);
        $table->addColumn('bar', 'string', ['notnull' => false, 'length' => 255]);
        $table->addUniqueIndex(['foo', 'bar']);
183

Sergei Morozov's avatar
Sergei Morozov committed
184
        $sql = $this->platform->getCreateTableSQL($table);
185
        self::assertEquals($this->getGenerateTableWithMultiColumnUniqueIndexSql(), $sql);
186 187
    }

188 189 190 191
    /**
     * @return string[]
     */
    abstract public function getGenerateTableWithMultiColumnUniqueIndexSql() : array;
192

193
    public function testGeneratesIndexCreationSql() : void
194
    {
Sergei Morozov's avatar
Sergei Morozov committed
195
        $indexDef = new Index('my_idx', ['user_name', 'last_login']);
196

197
        self::assertEquals(
198
            $this->getGenerateIndexSql(),
Sergei Morozov's avatar
Sergei Morozov committed
199
            $this->platform->getCreateIndexSQL($indexDef, 'mytable')
200 201 202
        );
    }

203
    abstract public function getGenerateIndexSql() : string;
204

205
    public function testGeneratesUniqueIndexCreationSql() : void
206
    {
Sergei Morozov's avatar
Sergei Morozov committed
207
        $indexDef = new Index('index_name', ['test', 'test2'], true);
208

Sergei Morozov's avatar
Sergei Morozov committed
209
        $sql = $this->platform->getCreateIndexSQL($indexDef, 'test');
210
        self::assertEquals($this->getGenerateUniqueIndexSql(), $sql);
211 212
    }

213
    abstract public function getGenerateUniqueIndexSql() : string;
214

215
    public function testGeneratesPartialIndexesSqlOnlyWhenSupportingPartialIndexes() : void
216
    {
217 218 219
        $where            = 'test IS NULL AND test2 IS NOT NULL';
        $indexDef         = new Index('name', ['test', 'test2'], false, false, [], ['where' => $where]);
        $uniqueConstraint = new UniqueConstraint('name', ['test', 'test2'], [], []);
220 221 222

        $expected = ' WHERE ' . $where;

Sergei Morozov's avatar
Sergei Morozov committed
223
        $actuals = [];
224 225

        if ($this->supportsInlineIndexDeclaration()) {
Sergei Morozov's avatar
Sergei Morozov committed
226
            $actuals[] = $this->platform->getIndexDeclarationSQL('name', $indexDef);
227 228
        }

229 230 231 232
        $uniqueConstraintSQL = $this->platform->getUniqueConstraintDeclarationSQL('name', $uniqueConstraint);
        $indexSQL            = $this->platform->getCreateIndexSQL($indexDef, 'table');

        $this->assertStringEndsNotWith($expected, $uniqueConstraintSQL, 'WHERE clause should NOT be present');
233 234

        foreach ($actuals as $actual) {
Sergei Morozov's avatar
Sergei Morozov committed
235
            if ($this->platform->supportsPartialIndexes()) {
236
                self::assertStringEndsWith($expected, $indexSQL, 'WHERE clause should be present');
237
            } else {
238
                self::assertStringEndsNotWith($expected, $indexSQL, 'WHERE clause should NOT be present');
239 240 241 242
            }
        }
    }

243
    public function testGeneratesForeignKeyCreationSql() : void
244
    {
Sergei Morozov's avatar
Sergei Morozov committed
245
        $fk = new ForeignKeyConstraint(['fk_name_id'], 'other_table', ['id'], '');
246

Sergei Morozov's avatar
Sergei Morozov committed
247
        $sql = $this->platform->getCreateForeignKeySQL($fk, 'test');
248
        self::assertEquals($sql, $this->getGenerateForeignKeySql());
249 250
    }

251
    abstract public function getGenerateForeignKeySql() : string;
252

253
    public function testGeneratesConstraintCreationSql() : void
254
    {
255 256 257 258
        if (! $this->platform->supportsCreateDropForeignKeyConstraints()) {
            $this->markTestSkipped('Platform does not support creating or dropping foreign key constraints.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
259
        $idx = new Index('constraint_name', ['test'], true, false);
Sergei Morozov's avatar
Sergei Morozov committed
260
        $sql = $this->platform->getCreateConstraintSQL($idx, 'test');
261
        self::assertEquals($this->getGenerateConstraintUniqueIndexSql(), $sql);
262

Sergei Morozov's avatar
Sergei Morozov committed
263
        $pk  = new Index('constraint_name', ['test'], true, true);
Sergei Morozov's avatar
Sergei Morozov committed
264
        $sql = $this->platform->getCreateConstraintSQL($pk, 'test');
265
        self::assertEquals($this->getGenerateConstraintPrimaryIndexSql(), $sql);
266

Sergei Morozov's avatar
Sergei Morozov committed
267
        $fk  = new ForeignKeyConstraint(['fk_name'], 'foreign', ['id'], 'constraint_fk');
Sergei Morozov's avatar
Sergei Morozov committed
268
        $sql = $this->platform->getCreateConstraintSQL($fk, 'test');
269
        self::assertEquals($this->getGenerateConstraintForeignKeySql($fk), $sql);
270
    }
Fabio B. Silva's avatar
Fabio B. Silva committed
271

272
    protected function getBitAndComparisonExpressionSql(string $value1, string $value2) : string
273 274 275 276 277 278 279
    {
        return '(' . $value1 . ' & ' . $value2 . ')';
    }

    /**
     * @group DDC-1213
     */
280
    public function testGeneratesBitAndComparisonExpressionSql() : void
281
    {
Sergei Morozov's avatar
Sergei Morozov committed
282
        $sql = $this->platform->getBitAndComparisonExpression(2, 4);
283
        self::assertEquals($this->getBitAndComparisonExpressionSql(2, 4), $sql);
284 285
    }

286
    protected function getBitOrComparisonExpressionSql(string $value1, string $value2) : string
287 288 289 290 291 292 293
    {
        return '(' . $value1 . ' | ' . $value2 . ')';
    }

    /**
     * @group DDC-1213
     */
294
    public function testGeneratesBitOrComparisonExpressionSql() : void
295
    {
Sergei Morozov's avatar
Sergei Morozov committed
296
        $sql = $this->platform->getBitOrComparisonExpression(2, 4);
297
        self::assertEquals($this->getBitOrComparisonExpressionSql(2, 4), $sql);
298
    }
299

300
    public function getGenerateConstraintUniqueIndexSql() : string
301 302 303 304
    {
        return 'ALTER TABLE test ADD CONSTRAINT constraint_name UNIQUE (test)';
    }

305
    public function getGenerateConstraintPrimaryIndexSql() : string
306 307 308 309
    {
        return 'ALTER TABLE test ADD CONSTRAINT constraint_name PRIMARY KEY (test)';
    }

310
    public function getGenerateConstraintForeignKeySql(ForeignKeyConstraint $fk) : string
311
    {
Sergei Morozov's avatar
Sergei Morozov committed
312
        $quotedForeignTable = $fk->getQuotedForeignTableName($this->platform);
313

Sergei Morozov's avatar
Sergei Morozov committed
314 315 316 317
        return sprintf(
            'ALTER TABLE test ADD CONSTRAINT constraint_fk FOREIGN KEY (fk_name) REFERENCES %s (id)',
            $quotedForeignTable
        );
318
    }
319

320 321 322 323
    /**
     * @return string[]
     */
    abstract public function getGenerateAlterTableSql() : array;
324

325
    public function testGeneratesTableAlterationSql() : void
326 327 328
    {
        $expectedSql = $this->getGenerateAlterTableSql();

329
        $table = new Table('mytable');
Sergei Morozov's avatar
Sergei Morozov committed
330
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
331 332 333
        $table->addColumn('foo', 'integer');
        $table->addColumn('bar', 'string');
        $table->addColumn('bloo', 'boolean');
Sergei Morozov's avatar
Sergei Morozov committed
334 335 336 337 338 339 340 341 342 343 344 345 346
        $table->setPrimaryKey(['id']);

        $tableDiff                         = new TableDiff('mytable');
        $tableDiff->fromTable              = $table;
        $tableDiff->newName                = 'userlist';
        $tableDiff->addedColumns['quota']  = new Column('quota', Type::getType('integer'), ['notnull' => false]);
        $tableDiff->removedColumns['foo']  = new Column('foo', Type::getType('integer'));
        $tableDiff->changedColumns['bar']  = new ColumnDiff(
            'bar',
            new Column(
                'baz',
                Type::getType('string'),
                ['default' => 'def']
347
            ),
Sergei Morozov's avatar
Sergei Morozov committed
348
            ['type', 'notnull', 'default']
349
        );
Sergei Morozov's avatar
Sergei Morozov committed
350 351 352 353 354 355
        $tableDiff->changedColumns['bloo'] = new ColumnDiff(
            'bloo',
            new Column(
                'bloo',
                Type::getType('boolean'),
                ['default' => false]
356
            ),
Sergei Morozov's avatar
Sergei Morozov committed
357
            ['type', 'notnull', 'default']
358
        );
359

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

362
        self::assertEquals($expectedSql, $sql);
363
    }
364

365
    public function testGetCustomColumnDeclarationSql() : void
366
    {
Sergei Morozov's avatar
Sergei Morozov committed
367
        $field = ['columnDefinition' => 'MEDIUMINT(6) UNSIGNED'];
Sergei Morozov's avatar
Sergei Morozov committed
368
        self::assertEquals('foo MEDIUMINT(6) UNSIGNED', $this->platform->getColumnDeclarationSQL('foo', $field));
369
    }
370

371
    public function testGetCreateTableSqlDispatchEvent() : void
372
    {
373 374
        $listenerMock = $this->getMockBuilder($this->getMockClass('GetCreateTableSqlDispatchEvenListener'))
            ->addMethods(['onSchemaCreateTable', 'onSchemaCreateTableColumn'])
375
            ->getMock();
376
        $listenerMock
377
            ->expects(self::once())
378
            ->method('onSchemaCreateTable');
379
        $listenerMock
380
            ->expects(self::exactly(2))
381 382 383
            ->method('onSchemaCreateTableColumn');

        $eventManager = new EventManager();
Sergei Morozov's avatar
Sergei Morozov committed
384
        $eventManager->addEventListener([Events::onSchemaCreateTable, Events::onSchemaCreateTableColumn], $listenerMock);
385

Sergei Morozov's avatar
Sergei Morozov committed
386
        $this->platform->setEventManager($eventManager);
387

388
        $table = new Table('test');
Sergei Morozov's avatar
Sergei Morozov committed
389 390
        $table->addColumn('foo', 'string', ['notnull' => false, 'length' => 255]);
        $table->addColumn('bar', 'string', ['notnull' => false, 'length' => 255]);
391

Sergei Morozov's avatar
Sergei Morozov committed
392
        $this->platform->getCreateTableSQL($table);
393 394
    }

395
    public function testGetDropTableSqlDispatchEvent() : void
396
    {
397 398
        $listenerMock = $this->getMockBuilder($this->getMockClass('GetDropTableSqlDispatchEventListener'))
            ->addMethods(['onSchemaDropTable'])
399
            ->getMock();
400
        $listenerMock
401
            ->expects(self::once())
402 403 404
            ->method('onSchemaDropTable');

        $eventManager = new EventManager();
Sergei Morozov's avatar
Sergei Morozov committed
405
        $eventManager->addEventListener([Events::onSchemaDropTable], $listenerMock);
406

Sergei Morozov's avatar
Sergei Morozov committed
407
        $this->platform->setEventManager($eventManager);
408

Sergei Morozov's avatar
Sergei Morozov committed
409
        $this->platform->getDropTableSQL('TABLE');
410
    }
411

412
    public function testGetAlterTableSqlDispatchEvent() : void
413
    {
Sergei Morozov's avatar
Sergei Morozov committed
414
        $events = [
415
            'onSchemaAlterTable',
jsor's avatar
jsor committed
416 417 418
            'onSchemaAlterTableAddColumn',
            'onSchemaAlterTableRemoveColumn',
            'onSchemaAlterTableChangeColumn',
Sergei Morozov's avatar
Sergei Morozov committed
419 420
            'onSchemaAlterTableRenameColumn',
        ];
421

422 423
        $listenerMock = $this->getMockBuilder($this->getMockClass('GetAlterTableSqlDispatchEvenListener'))
            ->addMethods($events)
424
            ->getMock();
425
        $listenerMock
426
            ->expects(self::once())
427
            ->method('onSchemaAlterTable');
428
        $listenerMock
429
            ->expects(self::once())
jsor's avatar
jsor committed
430
            ->method('onSchemaAlterTableAddColumn');
431
        $listenerMock
432
            ->expects(self::once())
jsor's avatar
jsor committed
433
            ->method('onSchemaAlterTableRemoveColumn');
434
        $listenerMock
435
            ->expects(self::once())
jsor's avatar
jsor committed
436
            ->method('onSchemaAlterTableChangeColumn');
437
        $listenerMock
438
            ->expects(self::once())
jsor's avatar
jsor committed
439
            ->method('onSchemaAlterTableRenameColumn');
440 441

        $eventManager = new EventManager();
Sergei Morozov's avatar
Sergei Morozov committed
442
        $events       = [
443
            Events::onSchemaAlterTable,
jsor's avatar
jsor committed
444 445 446
            Events::onSchemaAlterTableAddColumn,
            Events::onSchemaAlterTableRemoveColumn,
            Events::onSchemaAlterTableChangeColumn,
Sergei Morozov's avatar
Sergei Morozov committed
447 448
            Events::onSchemaAlterTableRenameColumn,
        ];
449 450
        $eventManager->addEventListener($events, $listenerMock);

Sergei Morozov's avatar
Sergei Morozov committed
451
        $this->platform->setEventManager($eventManager);
452

453 454 455 456 457
        $table = new Table('mytable');
        $table->addColumn('removed', 'integer');
        $table->addColumn('changed', 'integer');
        $table->addColumn('renamed', 'integer');

Sergei Morozov's avatar
Sergei Morozov committed
458 459 460 461 462 463 464 465 466 467
        $tableDiff                            = new TableDiff('mytable');
        $tableDiff->fromTable                 = $table;
        $tableDiff->addedColumns['added']     = new Column('added', Type::getType('integer'), []);
        $tableDiff->removedColumns['removed'] = new Column('removed', Type::getType('integer'), []);
        $tableDiff->changedColumns['changed'] = new ColumnDiff(
            'changed',
            new Column(
                'changed2',
                Type::getType('string'),
                []
468
            ),
Sergei Morozov's avatar
Sergei Morozov committed
469
            []
470
        );
Sergei Morozov's avatar
Sergei Morozov committed
471
        $tableDiff->renamedColumns['renamed'] = new Column('renamed2', Type::getType('integer'), []);
472

Sergei Morozov's avatar
Sergei Morozov committed
473
        $this->platform->getAlterTableSQL($tableDiff);
474
    }
475

476 477 478
    /**
     * @group DBAL-42
     */
479
    public function testCreateTableColumnComments() : void
480
    {
481
        $table = new Table('test');
Sergei Morozov's avatar
Sergei Morozov committed
482 483
        $table->addColumn('id', 'integer', ['comment' => 'This is a comment']);
        $table->setPrimaryKey(['id']);
484

Sergei Morozov's avatar
Sergei Morozov committed
485
        self::assertEquals($this->getCreateTableColumnCommentsSQL(), $this->platform->getCreateTableSQL($table));
486 487 488 489 490
    }

    /**
     * @group DBAL-42
     */
491
    public function testAlterTableColumnComments() : void
492
    {
Sergei Morozov's avatar
Sergei Morozov committed
493 494 495 496 497 498 499
        $tableDiff                        = new TableDiff('mytable');
        $tableDiff->addedColumns['quota'] = new Column('quota', Type::getType('integer'), ['comment' => 'A comment']);
        $tableDiff->changedColumns['foo'] = new ColumnDiff(
            'foo',
            new Column(
                'foo',
                Type::getType('string')
500
            ),
Sergei Morozov's avatar
Sergei Morozov committed
501
            ['comment']
502
        );
Sergei Morozov's avatar
Sergei Morozov committed
503 504 505 506 507 508
        $tableDiff->changedColumns['bar'] = new ColumnDiff(
            'bar',
            new Column(
                'baz',
                Type::getType('string'),
                ['comment' => 'B comment']
509
            ),
Sergei Morozov's avatar
Sergei Morozov committed
510
            ['comment']
511 512
        );

Sergei Morozov's avatar
Sergei Morozov committed
513
        self::assertEquals($this->getAlterTableColumnCommentsSQL(), $this->platform->getAlterTableSQL($tableDiff));
514 515
    }

516
    public function testCreateTableColumnTypeComments() : void
517
    {
518
        $table = new Table('test');
519 520
        $table->addColumn('id', 'integer');
        $table->addColumn('data', 'array');
Sergei Morozov's avatar
Sergei Morozov committed
521
        $table->setPrimaryKey(['id']);
522

Sergei Morozov's avatar
Sergei Morozov committed
523
        self::assertEquals($this->getCreateTableColumnTypeCommentsSQL(), $this->platform->getCreateTableSQL($table));
524 525
    }

526 527 528 529
    /**
     * @return string[]
     */
    public function getCreateTableColumnCommentsSQL() : array
530
    {
531
        self::markTestSkipped('Platform does not support Column comments.');
532 533
    }

534 535 536 537
    /**
     * @return string[]
     */
    public function getAlterTableColumnCommentsSQL() : array
538
    {
539
        self::markTestSkipped('Platform does not support Column comments.');
540
    }
541

542 543 544 545
    /**
     * @return string[]
     */
    public function getCreateTableColumnTypeCommentsSQL() : array
546
    {
547
        self::markTestSkipped('Platform does not support Column comments.');
548 549
    }

550
    public function testGetDefaultValueDeclarationSQL() : void
551 552
    {
        // non-timestamp value will get single quotes
Sergei Morozov's avatar
Sergei Morozov committed
553
        $field = [
554
            'type' => Type::getType('string'),
Sergei Morozov's avatar
Sergei Morozov committed
555 556
            'default' => 'non_timestamp',
        ];
557

Sergei Morozov's avatar
Sergei Morozov committed
558
        self::assertEquals(" DEFAULT 'non_timestamp'", $this->platform->getDefaultValueDeclarationSQL($field));
559 560
    }

561 562 563 564
    /**
     * @group 2859
     */
    public function testGetDefaultValueDeclarationSQLDateTime() : void
565 566
    {
        // timestamps on datetime types should not be quoted
567 568 569
        foreach (['datetime', 'datetimetz', 'datetime_immutable', 'datetimetz_immutable'] as $type) {
            $field = [
                'type'    => Type::getType($type),
Sergei Morozov's avatar
Sergei Morozov committed
570
                'default' => $this->platform->getCurrentTimestampSQL(),
571
            ];
572

573
            self::assertSame(
Sergei Morozov's avatar
Sergei Morozov committed
574 575
                ' DEFAULT ' . $this->platform->getCurrentTimestampSQL(),
                $this->platform->getDefaultValueDeclarationSQL($field)
576 577 578 579
            );
        }
    }

580
    public function testGetDefaultValueDeclarationSQLForIntegerTypes() : void
581
    {
Sergei Morozov's avatar
Sergei Morozov committed
582 583
        foreach (['bigint', 'integer', 'smallint'] as $type) {
            $field = [
584
                'type'    => Type::getType($type),
Sergei Morozov's avatar
Sergei Morozov committed
585 586
                'default' => 1,
            ];
587

588
            self::assertEquals(
589
                ' DEFAULT 1',
Sergei Morozov's avatar
Sergei Morozov committed
590
                $this->platform->getDefaultValueDeclarationSQL($field)
591 592 593 594
            );
        }
    }

595 596 597
    /**
     * @group 2859
     */
598
    public function testGetDefaultValueDeclarationSQLForDateType() : void
599
    {
Sergei Morozov's avatar
Sergei Morozov committed
600
        $currentDateSql = $this->platform->getCurrentDateSQL();
601 602 603 604 605 606 607 608
        foreach (['date', 'date_immutable'] as $type) {
            $field = [
                'type'    => Type::getType($type),
                'default' => $currentDateSql,
            ];

            self::assertSame(
                ' DEFAULT ' . $currentDateSql,
Sergei Morozov's avatar
Sergei Morozov committed
609
                $this->platform->getDefaultValueDeclarationSQL($field)
610 611
            );
        }
612 613
    }

614 615 616
    /**
     * @group DBAL-45
     */
617
    public function testKeywordList() : void
618
    {
Sergei Morozov's avatar
Sergei Morozov committed
619 620
        $keywordList = $this->platform->getReservedKeywordsList();
        self::assertInstanceOf(KeywordList::class, $keywordList);
621

622
        self::assertTrue($keywordList->isKeyword('table'));
623
    }
624 625 626 627

    /**
     * @group DBAL-374
     */
628
    public function testQuotedColumnInPrimaryKeyPropagation() : void
629 630
    {
        $table = new Table('`quoted`');
631
        $table->addColumn('create', 'string');
Sergei Morozov's avatar
Sergei Morozov committed
632
        $table->setPrimaryKey(['create']);
633

Sergei Morozov's avatar
Sergei Morozov committed
634
        $sql = $this->platform->getCreateTableSQL($table);
635
        self::assertEquals($this->getQuotedColumnInPrimaryKeySQL(), $sql);
636 637
    }

638 639 640 641
    /**
     * @return string[]
     */
    abstract protected function getQuotedColumnInPrimaryKeySQL() : array;
642

643 644 645 646
    /**
     * @return string[]
     */
    abstract protected function getQuotedColumnInIndexSQL() : array;
647

648 649 650 651
    /**
     * @return string[]
     */
    abstract protected function getQuotedNameInIndexSQL() : array;
652

653 654 655 656
    /**
     * @return string[]
     */
    abstract protected function getQuotedColumnInForeignKeySQL() : array;
657 658 659 660

    /**
     * @group DBAL-374
     */
661
    public function testQuotedColumnInIndexPropagation() : void
662 663
    {
        $table = new Table('`quoted`');
664
        $table->addColumn('create', 'string');
Sergei Morozov's avatar
Sergei Morozov committed
665
        $table->addIndex(['create']);
666

Sergei Morozov's avatar
Sergei Morozov committed
667
        $sql = $this->platform->getCreateTableSQL($table);
668
        self::assertEquals($this->getQuotedColumnInIndexSQL(), $sql);
669
    }
670

671
    public function testQuotedNameInIndexSQL() : void
Markus Fasselt's avatar
Markus Fasselt committed
672 673 674
    {
        $table = new Table('test');
        $table->addColumn('column1', 'string');
Sergei Morozov's avatar
Sergei Morozov committed
675
        $table->addIndex(['column1'], '`key`');
Markus Fasselt's avatar
Markus Fasselt committed
676

Sergei Morozov's avatar
Sergei Morozov committed
677
        $sql = $this->platform->getCreateTableSQL($table);
678
        self::assertEquals($this->getQuotedNameInIndexSQL(), $sql);
Markus Fasselt's avatar
Markus Fasselt committed
679 680
    }

681 682 683
    /**
     * @group DBAL-374
     */
684
    public function testQuotedColumnInForeignKeyPropagation() : void
685 686 687 688
    {
        $table = new Table('`quoted`');
        $table->addColumn('create', 'string');
        $table->addColumn('foo', 'string');
689
        $table->addColumn('`bar`', 'string');
690

691
        // Foreign table with reserved keyword as name (needs quotation).
692
        $foreignTable = new Table('foreign');
693 694 695
        $foreignTable->addColumn('create', 'string');    // Foreign column with reserved keyword as name (needs quotation).
        $foreignTable->addColumn('bar', 'string');       // Foreign column with non-reserved keyword as name (does not need quotation).
        $foreignTable->addColumn('`foo-bar`', 'string'); // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite).
696

Sergei Morozov's avatar
Sergei Morozov committed
697
        $table->addForeignKeyConstraint($foreignTable, ['create', 'foo', '`bar`'], ['create', 'bar', '`foo-bar`'], [], 'FK_WITH_RESERVED_KEYWORD');
698 699 700 701 702 703 704

        // Foreign table with non-reserved keyword as name (does not need quotation).
        $foreignTable = new Table('foo');
        $foreignTable->addColumn('create', 'string');    // Foreign column with reserved keyword as name (needs quotation).
        $foreignTable->addColumn('bar', 'string');       // Foreign column with non-reserved keyword as name (does not need quotation).
        $foreignTable->addColumn('`foo-bar`', 'string'); // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite).

Sergei Morozov's avatar
Sergei Morozov committed
705
        $table->addForeignKeyConstraint($foreignTable, ['create', 'foo', '`bar`'], ['create', 'bar', '`foo-bar`'], [], 'FK_WITH_NON_RESERVED_KEYWORD');
706 707 708 709 710 711 712

        // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite).
        $foreignTable = new Table('`foo-bar`');
        $foreignTable->addColumn('create', 'string');    // Foreign column with reserved keyword as name (needs quotation).
        $foreignTable->addColumn('bar', 'string');       // Foreign column with non-reserved keyword as name (does not need quotation).
        $foreignTable->addColumn('`foo-bar`', 'string'); // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite).

Sergei Morozov's avatar
Sergei Morozov committed
713
        $table->addForeignKeyConstraint($foreignTable, ['create', 'foo', '`bar`'], ['create', 'bar', '`foo-bar`'], [], 'FK_WITH_INTENDED_QUOTATION');
714

Sergei Morozov's avatar
Sergei Morozov committed
715
        $sql = $this->platform->getCreateTableSQL($table, AbstractPlatform::CREATE_FOREIGNKEYS);
716
        self::assertEquals($this->getQuotedColumnInForeignKeySQL(), $sql);
717
    }
718

719 720 721
    /**
     * @group DBAL-1051
     */
722
    public function testQuotesReservedKeywordInUniqueConstraintDeclarationSQL() : void
723
    {
724
        $constraint = new UniqueConstraint('select', ['foo'], [], []);
725

726
        self::assertSame(
727
            $this->getQuotesReservedKeywordInUniqueConstraintDeclarationSQL(),
728
            $this->platform->getUniqueConstraintDeclarationSQL('select', $constraint)
729 730 731
        );
    }

732
    abstract protected function getQuotesReservedKeywordInUniqueConstraintDeclarationSQL() : string;
733

734 735 736
    /**
     * @group DBAL-2270
     */
737
    public function testQuotesReservedKeywordInTruncateTableSQL() : void
738
    {
739
        self::assertSame(
740
            $this->getQuotesReservedKeywordInTruncateTableSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
741
            $this->platform->getTruncateTableSQL('select')
742 743 744
        );
    }

745
    abstract protected function getQuotesReservedKeywordInTruncateTableSQL() : string;
746

747 748 749
    /**
     * @group DBAL-1051
     */
750
    public function testQuotesReservedKeywordInIndexDeclarationSQL() : void
751
    {
Sergei Morozov's avatar
Sergei Morozov committed
752
        $index = new Index('select', ['foo']);
753 754

        if (! $this->supportsInlineIndexDeclaration()) {
Sergei Morozov's avatar
Sergei Morozov committed
755
            $this->expectException(DBALException::class);
756 757
        }

758
        self::assertSame(
759
            $this->getQuotesReservedKeywordInIndexDeclarationSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
760
            $this->platform->getIndexDeclarationSQL('select', $index)
761 762 763
        );
    }

764
    abstract protected function getQuotesReservedKeywordInIndexDeclarationSQL() : string;
765

766
    protected function supportsInlineIndexDeclaration() : bool
767 768 769 770
    {
        return true;
    }

771
    public function testSupportsCommentOnStatement() : void
772
    {
Sergei Morozov's avatar
Sergei Morozov committed
773
        self::assertSame($this->supportsCommentOnStatement(), $this->platform->supportsCommentOnStatement());
774 775
    }

776
    protected function supportsCommentOnStatement() : bool
777 778 779 780
    {
        return false;
    }

781
    public function testGetCreateSchemaSQL() : void
782
    {
783 784
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
785
        $this->platform->getCreateSchemaSQL('schema');
786 787
    }

788 789 790
    /**
     * @group DBAL-585
     */
791
    public function testAlterTableChangeQuotedColumn() : void
792
    {
Sergei Morozov's avatar
Sergei Morozov committed
793 794 795 796 797 798 799
        $tableDiff                        = new TableDiff('mytable');
        $tableDiff->fromTable             = new Table('mytable');
        $tableDiff->changedColumns['foo'] = new ColumnDiff(
            'select',
            new Column(
                'select',
                Type::getType('string')
800
            ),
Sergei Morozov's avatar
Sergei Morozov committed
801
            ['type']
802 803
        );

804
        self::assertStringContainsString(
Sergei Morozov's avatar
Sergei Morozov committed
805 806
            $this->platform->quoteIdentifier('select'),
            implode(';', $this->platform->getAlterTableSQL($tableDiff))
807 808
        );
    }
Benjamin Eberlei's avatar
Benjamin Eberlei committed
809

810 811 812
    /**
     * @group DBAL-563
     */
813
    public function testUsesSequenceEmulatedIdentityColumns() : void
814
    {
Sergei Morozov's avatar
Sergei Morozov committed
815
        self::assertFalse($this->platform->usesSequenceEmulatedIdentityColumns());
816 817
    }

818 819
    public function testGetSequencePrefixWithoutSchema() : void
    {
Guilherme Blanco's avatar
Guilherme Blanco committed
820
        self::assertEquals('foo', $this->platform->getSequencePrefix('foo'));
821 822 823 824
    }

    public function testGetSequencePrefixWithSchema() : void
    {
Guilherme Blanco's avatar
Guilherme Blanco committed
825
        self::assertEquals('bar.foo', $this->platform->getSequencePrefix('foo', 'bar'));
826 827
    }

828 829 830
    /**
     * @group DBAL-563
     */
831
    public function testReturnsIdentitySequenceName() : void
832
    {
833 834
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
835
        $this->platform->getIdentitySequenceName('mytable', 'mycolumn');
836
    }
Steve Müller's avatar
Steve Müller committed
837

838
    public function testReturnsBinaryDefaultLength() : void
Steve Müller's avatar
Steve Müller committed
839
    {
Sergei Morozov's avatar
Sergei Morozov committed
840
        self::assertSame($this->getBinaryDefaultLength(), $this->platform->getBinaryDefaultLength());
Steve Müller's avatar
Steve Müller committed
841 842
    }

843
    protected function getBinaryDefaultLength() : int
Steve Müller's avatar
Steve Müller committed
844 845 846 847
    {
        return 255;
    }

848
    public function testReturnsBinaryMaxLength() : void
Steve Müller's avatar
Steve Müller committed
849
    {
Sergei Morozov's avatar
Sergei Morozov committed
850
        self::assertSame($this->getBinaryMaxLength(), $this->platform->getBinaryMaxLength());
Steve Müller's avatar
Steve Müller committed
851 852
    }

853
    protected function getBinaryMaxLength() : int
Steve Müller's avatar
Steve Müller committed
854 855 856 857
    {
        return 4000;
    }

858
    public function testReturnsBinaryTypeDeclarationSQL() : void
Steve Müller's avatar
Steve Müller committed
859
    {
860 861
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
862
        $this->platform->getBinaryTypeDeclarationSQL([]);
Steve Müller's avatar
Steve Müller committed
863
    }
864

865
    public function testReturnsBinaryTypeLongerThanMaxDeclarationSQL() : void
866 867 868 869
    {
        $this->markTestSkipped('Not applicable to the platform');
    }

870 871 872
    /**
     * @group DBAL-553
     */
873
    public function hasNativeJsonType() : void
874
    {
Sergei Morozov's avatar
Sergei Morozov committed
875
        self::assertFalse($this->platform->hasNativeJsonType());
876 877 878 879 880
    }

    /**
     * @group DBAL-553
     */
881
    public function testReturnsJsonTypeDeclarationSQL() : void
882
    {
Sergei Morozov's avatar
Sergei Morozov committed
883
        $column = [
884 885 886
            'length'  => 666,
            'notnull' => true,
            'type'    => Type::getType('json_array'),
Sergei Morozov's avatar
Sergei Morozov committed
887
        ];
888

889
        self::assertSame(
Sergei Morozov's avatar
Sergei Morozov committed
890 891
            $this->platform->getClobTypeDeclarationSQL($column),
            $this->platform->getJsonTypeDeclarationSQL($column)
892 893
        );
    }
894 895 896 897

    /**
     * @group DBAL-234
     */
898
    public function testAlterTableRenameIndex() : void
899
    {
Sergei Morozov's avatar
Sergei Morozov committed
900
        $tableDiff            = new TableDiff('mytable');
901 902
        $tableDiff->fromTable = new Table('mytable');
        $tableDiff->fromTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
903 904 905 906
        $tableDiff->fromTable->setPrimaryKey(['id']);
        $tableDiff->renamedIndexes = [
            'idx_foo' => new Index('idx_bar', ['id']),
        ];
907

908
        self::assertSame(
909
            $this->getAlterTableRenameIndexSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
910
            $this->platform->getAlterTableSQL($tableDiff)
911 912 913 914
        );
    }

    /**
915 916
     * @return string[]
     *
917 918
     * @group DBAL-234
     */
919
    protected function getAlterTableRenameIndexSQL() : array
920
    {
Sergei Morozov's avatar
Sergei Morozov committed
921
        return [
922 923
            'DROP INDEX idx_foo',
            'CREATE INDEX idx_bar ON mytable (id)',
Sergei Morozov's avatar
Sergei Morozov committed
924
        ];
925 926 927 928 929
    }

    /**
     * @group DBAL-234
     */
930
    public function testQuotesAlterTableRenameIndex() : void
931
    {
Sergei Morozov's avatar
Sergei Morozov committed
932
        $tableDiff            = new TableDiff('table');
933 934
        $tableDiff->fromTable = new Table('table');
        $tableDiff->fromTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
935 936 937 938 939
        $tableDiff->fromTable->setPrimaryKey(['id']);
        $tableDiff->renamedIndexes = [
            'create' => new Index('select', ['id']),
            '`foo`'  => new Index('`bar`', ['id']),
        ];
940

941
        self::assertSame(
942
            $this->getQuotedAlterTableRenameIndexSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
943
            $this->platform->getAlterTableSQL($tableDiff)
944 945 946 947
        );
    }

    /**
948 949
     * @return string[]
     *
950 951
     * @group DBAL-234
     */
952
    protected function getQuotedAlterTableRenameIndexSQL() : array
953
    {
Sergei Morozov's avatar
Sergei Morozov committed
954
        return [
955 956 957 958
            'DROP INDEX "create"',
            'CREATE INDEX "select" ON "table" (id)',
            'DROP INDEX "foo"',
            'CREATE INDEX "bar" ON "table" (id)',
Sergei Morozov's avatar
Sergei Morozov committed
959
        ];
960
    }
961 962 963 964

    /**
     * @group DBAL-835
     */
965
    public function testQuotesAlterTableRenameColumn() : void
966 967 968
    {
        $fromTable = new Table('mytable');

Sergei Morozov's avatar
Sergei Morozov committed
969 970 971
        $fromTable->addColumn('unquoted1', 'integer', ['comment' => 'Unquoted 1']);
        $fromTable->addColumn('unquoted2', 'integer', ['comment' => 'Unquoted 2']);
        $fromTable->addColumn('unquoted3', 'integer', ['comment' => 'Unquoted 3']);
972

Sergei Morozov's avatar
Sergei Morozov committed
973 974 975
        $fromTable->addColumn('create', 'integer', ['comment' => 'Reserved keyword 1']);
        $fromTable->addColumn('table', 'integer', ['comment' => 'Reserved keyword 2']);
        $fromTable->addColumn('select', 'integer', ['comment' => 'Reserved keyword 3']);
976

Sergei Morozov's avatar
Sergei Morozov committed
977 978 979
        $fromTable->addColumn('`quoted1`', 'integer', ['comment' => 'Quoted 1']);
        $fromTable->addColumn('`quoted2`', 'integer', ['comment' => 'Quoted 2']);
        $fromTable->addColumn('`quoted3`', 'integer', ['comment' => 'Quoted 3']);
980 981 982

        $toTable = new Table('mytable');

Sergei Morozov's avatar
Sergei Morozov committed
983 984 985
        $toTable->addColumn('unquoted', 'integer', ['comment' => 'Unquoted 1']); // unquoted -> unquoted
        $toTable->addColumn('where', 'integer', ['comment' => 'Unquoted 2']); // unquoted -> reserved keyword
        $toTable->addColumn('`foo`', 'integer', ['comment' => 'Unquoted 3']); // unquoted -> quoted
986

Sergei Morozov's avatar
Sergei Morozov committed
987 988 989
        $toTable->addColumn('reserved_keyword', 'integer', ['comment' => 'Reserved keyword 1']); // reserved keyword -> unquoted
        $toTable->addColumn('from', 'integer', ['comment' => 'Reserved keyword 2']); // reserved keyword -> reserved keyword
        $toTable->addColumn('`bar`', 'integer', ['comment' => 'Reserved keyword 3']); // reserved keyword -> quoted
990

Sergei Morozov's avatar
Sergei Morozov committed
991 992 993
        $toTable->addColumn('quoted', 'integer', ['comment' => 'Quoted 1']); // quoted -> unquoted
        $toTable->addColumn('and', 'integer', ['comment' => 'Quoted 2']); // quoted -> reserved keyword
        $toTable->addColumn('`baz`', 'integer', ['comment' => 'Quoted 3']); // quoted -> quoted
994 995 996

        $comparator = new Comparator();

997
        self::assertEquals(
998
            $this->getQuotedAlterTableRenameColumnSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
999
            $this->platform->getAlterTableSQL($comparator->diffTable($fromTable, $toTable))
1000 1001 1002 1003 1004 1005
        );
    }

    /**
     * Returns SQL statements for {@link testQuotesAlterTableRenameColumn}.
     *
Sergei Morozov's avatar
Sergei Morozov committed
1006
     * @return string[]
1007 1008 1009
     *
     * @group DBAL-835
     */
1010
    abstract protected function getQuotedAlterTableRenameColumnSQL() : array;
1011

1012 1013 1014
    /**
     * @group DBAL-835
     */
1015
    public function testQuotesAlterTableChangeColumnLength() : void
1016 1017 1018
    {
        $fromTable = new Table('mytable');

Sergei Morozov's avatar
Sergei Morozov committed
1019 1020 1021
        $fromTable->addColumn('unquoted1', 'string', ['comment' => 'Unquoted 1', 'length' => 10]);
        $fromTable->addColumn('unquoted2', 'string', ['comment' => 'Unquoted 2', 'length' => 10]);
        $fromTable->addColumn('unquoted3', 'string', ['comment' => 'Unquoted 3', 'length' => 10]);
1022

Sergei Morozov's avatar
Sergei Morozov committed
1023 1024 1025
        $fromTable->addColumn('create', 'string', ['comment' => 'Reserved keyword 1', 'length' => 10]);
        $fromTable->addColumn('table', 'string', ['comment' => 'Reserved keyword 2', 'length' => 10]);
        $fromTable->addColumn('select', 'string', ['comment' => 'Reserved keyword 3', 'length' => 10]);
1026 1027 1028

        $toTable = new Table('mytable');

Sergei Morozov's avatar
Sergei Morozov committed
1029 1030 1031
        $toTable->addColumn('unquoted1', 'string', ['comment' => 'Unquoted 1', 'length' => 255]);
        $toTable->addColumn('unquoted2', 'string', ['comment' => 'Unquoted 2', 'length' => 255]);
        $toTable->addColumn('unquoted3', 'string', ['comment' => 'Unquoted 3', 'length' => 255]);
1032

Sergei Morozov's avatar
Sergei Morozov committed
1033 1034 1035
        $toTable->addColumn('create', 'string', ['comment' => 'Reserved keyword 1', 'length' => 255]);
        $toTable->addColumn('table', 'string', ['comment' => 'Reserved keyword 2', 'length' => 255]);
        $toTable->addColumn('select', 'string', ['comment' => 'Reserved keyword 3', 'length' => 255]);
1036 1037 1038

        $comparator = new Comparator();

1039
        self::assertEquals(
1040
            $this->getQuotedAlterTableChangeColumnLengthSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1041
            $this->platform->getAlterTableSQL($comparator->diffTable($fromTable, $toTable))
1042 1043 1044 1045 1046 1047
        );
    }

    /**
     * Returns SQL statements for {@link testQuotesAlterTableChangeColumnLength}.
     *
Sergei Morozov's avatar
Sergei Morozov committed
1048
     * @return string[]
1049 1050 1051
     *
     * @group DBAL-835
     */
1052
    abstract protected function getQuotedAlterTableChangeColumnLengthSQL() : array;
1053

1054 1055 1056
    /**
     * @group DBAL-807
     */
1057
    public function testAlterTableRenameIndexInSchema() : void
1058
    {
Sergei Morozov's avatar
Sergei Morozov committed
1059
        $tableDiff            = new TableDiff('myschema.mytable');
1060 1061
        $tableDiff->fromTable = new Table('myschema.mytable');
        $tableDiff->fromTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
1062 1063 1064 1065
        $tableDiff->fromTable->setPrimaryKey(['id']);
        $tableDiff->renamedIndexes = [
            'idx_foo' => new Index('idx_bar', ['id']),
        ];
1066

1067
        self::assertSame(
1068
            $this->getAlterTableRenameIndexInSchemaSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1069
            $this->platform->getAlterTableSQL($tableDiff)
1070 1071 1072 1073
        );
    }

    /**
1074 1075
     * @return string[]
     *
1076 1077
     * @group DBAL-807
     */
1078
    protected function getAlterTableRenameIndexInSchemaSQL() : array
1079
    {
Sergei Morozov's avatar
Sergei Morozov committed
1080
        return [
1081 1082
            'DROP INDEX idx_foo',
            'CREATE INDEX idx_bar ON myschema.mytable (id)',
Sergei Morozov's avatar
Sergei Morozov committed
1083
        ];
1084 1085 1086 1087 1088
    }

    /**
     * @group DBAL-807
     */
1089
    public function testQuotesAlterTableRenameIndexInSchema() : void
1090
    {
Sergei Morozov's avatar
Sergei Morozov committed
1091
        $tableDiff            = new TableDiff('`schema`.table');
1092 1093
        $tableDiff->fromTable = new Table('`schema`.table');
        $tableDiff->fromTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
1094 1095 1096 1097 1098
        $tableDiff->fromTable->setPrimaryKey(['id']);
        $tableDiff->renamedIndexes = [
            'create' => new Index('select', ['id']),
            '`foo`'  => new Index('`bar`', ['id']),
        ];
1099

1100
        self::assertSame(
1101
            $this->getQuotedAlterTableRenameIndexInSchemaSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1102
            $this->platform->getAlterTableSQL($tableDiff)
1103 1104 1105 1106
        );
    }

    /**
1107 1108
     * @return string[]
     *
1109 1110
     * @group DBAL-234
     */
1111
    protected function getQuotedAlterTableRenameIndexInSchemaSQL() : array
1112
    {
Sergei Morozov's avatar
Sergei Morozov committed
1113
        return [
1114 1115 1116 1117
            'DROP INDEX "schema"."create"',
            'CREATE INDEX "select" ON "schema"."table" (id)',
            'DROP INDEX "schema"."foo"',
            'CREATE INDEX "bar" ON "schema"."table" (id)',
Sergei Morozov's avatar
Sergei Morozov committed
1118
        ];
1119
    }
1120

1121 1122 1123
    /**
     * @group DBAL-1237
     */
1124
    public function testQuotesDropForeignKeySQL() : void
1125
    {
1126
        if (! $this->platform->supportsCreateDropForeignKeyConstraints()) {
1127
            self::markTestSkipped(
1128
                sprintf('%s does not support modifying foreign key constraints.', get_class($this->platform))
1129 1130 1131
            );
        }

Sergei Morozov's avatar
Sergei Morozov committed
1132 1133
        $tableName      = 'table';
        $table          = new Table($tableName);
1134
        $foreignKeyName = 'select';
Sergei Morozov's avatar
Sergei Morozov committed
1135 1136
        $foreignKey     = new ForeignKeyConstraint([], 'foo', [], 'select');
        $expectedSql    = $this->getQuotesDropForeignKeySQL();
1137

Sergei Morozov's avatar
Sergei Morozov committed
1138 1139
        self::assertSame($expectedSql, $this->platform->getDropForeignKeySQL($foreignKeyName, $tableName));
        self::assertSame($expectedSql, $this->platform->getDropForeignKeySQL($foreignKey, $table));
1140 1141
    }

1142
    protected function getQuotesDropForeignKeySQL() : string
1143 1144 1145 1146 1147 1148 1149
    {
        return 'ALTER TABLE "table" DROP FOREIGN KEY "select"';
    }

    /**
     * @group DBAL-1237
     */
1150
    public function testQuotesDropConstraintSQL() : void
1151
    {
Sergei Morozov's avatar
Sergei Morozov committed
1152 1153
        $tableName      = 'table';
        $table          = new Table($tableName);
1154
        $constraintName = 'select';
Sergei Morozov's avatar
Sergei Morozov committed
1155 1156
        $constraint     = new ForeignKeyConstraint([], 'foo', [], 'select');
        $expectedSql    = $this->getQuotesDropConstraintSQL();
1157

Sergei Morozov's avatar
Sergei Morozov committed
1158 1159
        self::assertSame($expectedSql, $this->platform->getDropConstraintSQL($constraintName, $tableName));
        self::assertSame($expectedSql, $this->platform->getDropConstraintSQL($constraint, $table));
1160 1161
    }

1162
    protected function getQuotesDropConstraintSQL() : string
1163 1164 1165 1166
    {
        return 'ALTER TABLE "table" DROP CONSTRAINT "select"';
    }

1167
    protected function getStringLiteralQuoteCharacter() : string
1168 1169 1170 1171
    {
        return "'";
    }

1172
    public function testGetStringLiteralQuoteCharacter() : void
1173
    {
Sergei Morozov's avatar
Sergei Morozov committed
1174
        self::assertSame($this->getStringLiteralQuoteCharacter(), $this->platform->getStringLiteralQuoteCharacter());
1175 1176
    }

1177
    protected function getQuotedCommentOnColumnSQLWithoutQuoteCharacter() : string
1178 1179 1180 1181
    {
        return "COMMENT ON COLUMN mytable.id IS 'This is a comment'";
    }

1182
    public function testGetCommentOnColumnSQLWithoutQuoteCharacter() : void
1183
    {
1184
        self::assertEquals(
1185
            $this->getQuotedCommentOnColumnSQLWithoutQuoteCharacter(),
Sergei Morozov's avatar
Sergei Morozov committed
1186
            $this->platform->getCommentOnColumnSQL('mytable', 'id', 'This is a comment')
1187
        );
1188 1189
    }

1190
    protected function getQuotedCommentOnColumnSQLWithQuoteCharacter() : string
1191 1192 1193 1194
    {
        return "COMMENT ON COLUMN mytable.id IS 'It''s a quote !'";
    }

1195
    public function testGetCommentOnColumnSQLWithQuoteCharacter() : void
1196 1197
    {
        $c = $this->getStringLiteralQuoteCharacter();
1198

1199
        self::assertEquals(
1200
            $this->getQuotedCommentOnColumnSQLWithQuoteCharacter(),
Sergei Morozov's avatar
Sergei Morozov committed
1201
            $this->platform->getCommentOnColumnSQL('mytable', 'id', 'It' . $c . 's a quote !')
1202 1203 1204
        );
    }

1205 1206
    /**
     * @see testGetCommentOnColumnSQL
Sergei Morozov's avatar
Sergei Morozov committed
1207
     *
Sergei Morozov's avatar
Sergei Morozov committed
1208
     * @return string[]
1209
     */
1210
    abstract protected function getCommentOnColumnSQL() : array;
1211 1212 1213 1214

    /**
     * @group DBAL-1004
     */
1215
    public function testGetCommentOnColumnSQL() : void
1216
    {
1217
        self::assertSame(
1218
            $this->getCommentOnColumnSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1219
            [
Sergei Morozov's avatar
Sergei Morozov committed
1220 1221 1222
                $this->platform->getCommentOnColumnSQL('foo', 'bar', 'comment'), // regular identifiers
                $this->platform->getCommentOnColumnSQL('`Foo`', '`BAR`', 'comment'), // explicitly quoted identifiers
                $this->platform->getCommentOnColumnSQL('select', 'from', 'comment'), // reserved keyword identifiers
Sergei Morozov's avatar
Sergei Morozov committed
1223
            ]
1224 1225 1226
        );
    }

1227 1228 1229 1230
    /**
     * @group DBAL-1176
     * @dataProvider getGeneratesInlineColumnCommentSQL
     */
1231
    public function testGeneratesInlineColumnCommentSQL(?string $comment, string $expectedSql) : void
1232
    {
Sergei Morozov's avatar
Sergei Morozov committed
1233
        if (! $this->platform->supportsInlineColumnComments()) {
1234
            self::markTestSkipped(sprintf('%s does not support inline column comments.', get_class($this->platform)));
1235 1236
        }

Sergei Morozov's avatar
Sergei Morozov committed
1237
        self::assertSame($expectedSql, $this->platform->getInlineColumnCommentSQL($comment));
1238 1239
    }

1240 1241 1242 1243
    /**
     * @return mixed[][]
     */
    public static function getGeneratesInlineColumnCommentSQL() : iterable
1244
    {
Sergei Morozov's avatar
Sergei Morozov committed
1245
        return [
1246
            'regular comment' => ['Regular comment', static::getInlineColumnRegularCommentSQL()],
Sergei Morozov's avatar
Sergei Morozov committed
1247
            'comment requiring escaping' => [
1248 1249
                sprintf(
                    'Using inline comment delimiter %s works',
1250
                    static::getInlineColumnCommentDelimiter()
1251
                ),
1252
                static::getInlineColumnCommentRequiringEscapingSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1253
            ],
1254
            'empty comment' => ['', static::getInlineColumnEmptyCommentSQL()],
Sergei Morozov's avatar
Sergei Morozov committed
1255
        ];
1256 1257
    }

1258
    protected static function getInlineColumnCommentDelimiter() : string
1259 1260 1261 1262
    {
        return "'";
    }

1263
    protected static function getInlineColumnRegularCommentSQL() : string
1264 1265 1266 1267
    {
        return "COMMENT 'Regular comment'";
    }

1268
    protected static function getInlineColumnCommentRequiringEscapingSQL() : string
1269 1270 1271 1272
    {
        return "COMMENT 'Using inline comment delimiter '' works'";
    }

1273
    protected static function getInlineColumnEmptyCommentSQL() : string
1274 1275 1276 1277
    {
        return "COMMENT ''";
    }

1278
    protected function getQuotedStringLiteralWithoutQuoteCharacter() : string
1279 1280 1281 1282
    {
        return "'No quote'";
    }

1283
    protected function getQuotedStringLiteralWithQuoteCharacter() : string
1284 1285 1286 1287
    {
        return "'It''s a quote'";
    }

1288
    protected function getQuotedStringLiteralQuoteCharacter() : string
1289 1290 1291 1292
    {
        return "''''";
    }

1293 1294 1295
    /**
     * @group DBAL-1176
     */
1296
    public function testThrowsExceptionOnGeneratingInlineColumnCommentSQLIfUnsupported() : void
1297
    {
Sergei Morozov's avatar
Sergei Morozov committed
1298
        if ($this->platform->supportsInlineColumnComments()) {
1299
            self::markTestSkipped(sprintf('%s supports inline column comments.', get_class($this->platform)));
1300 1301
        }

Sergei Morozov's avatar
Sergei Morozov committed
1302
        $this->expectException(DBALException::class);
1303 1304
        $this->expectExceptionMessage("Operation 'Doctrine\\DBAL\\Platforms\\AbstractPlatform::getInlineColumnCommentSQL' is not supported by platform.");
        $this->expectExceptionCode(0);
1305

Sergei Morozov's avatar
Sergei Morozov committed
1306
        $this->platform->getInlineColumnCommentSQL('unsupported');
1307 1308
    }

1309
    public function testQuoteStringLiteral() : void
1310
    {
1311 1312
        $c = $this->getStringLiteralQuoteCharacter();

1313
        self::assertEquals(
1314
            $this->getQuotedStringLiteralWithoutQuoteCharacter(),
Sergei Morozov's avatar
Sergei Morozov committed
1315
            $this->platform->quoteStringLiteral('No quote')
1316
        );
1317
        self::assertEquals(
1318
            $this->getQuotedStringLiteralWithQuoteCharacter(),
Sergei Morozov's avatar
Sergei Morozov committed
1319
            $this->platform->quoteStringLiteral('It' . $c . 's a quote')
1320
        );
1321
        self::assertEquals(
1322
            $this->getQuotedStringLiteralQuoteCharacter(),
Sergei Morozov's avatar
Sergei Morozov committed
1323
            $this->platform->quoteStringLiteral($c)
1324
        );
1325
    }
1326 1327 1328 1329

    /**
     * @group DBAL-423
     */
1330
    public function testReturnsGuidTypeDeclarationSQL() : void
1331
    {
1332 1333
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
1334
        $this->platform->getGuidTypeDeclarationSQL([]);
1335
    }
1336 1337 1338 1339

    /**
     * @group DBAL-1010
     */
1340
    public function testGeneratesAlterTableRenameColumnSQL() : void
1341 1342 1343 1344 1345
    {
        $table = new Table('foo');
        $table->addColumn(
            'bar',
            'integer',
Sergei Morozov's avatar
Sergei Morozov committed
1346
            ['notnull' => true, 'default' => 666, 'comment' => 'rename test']
1347 1348
        );

Sergei Morozov's avatar
Sergei Morozov committed
1349 1350
        $tableDiff                        = new TableDiff('foo');
        $tableDiff->fromTable             = $table;
1351 1352 1353
        $tableDiff->renamedColumns['bar'] = new Column(
            'baz',
            Type::getType('integer'),
Sergei Morozov's avatar
Sergei Morozov committed
1354
            ['notnull' => true, 'default' => 666, 'comment' => 'rename test']
1355 1356
        );

Sergei Morozov's avatar
Sergei Morozov committed
1357
        self::assertSame($this->getAlterTableRenameColumnSQL(), $this->platform->getAlterTableSQL($tableDiff));
1358 1359 1360
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1361
     * @return string[]
1362
     */
1363
    abstract public function getAlterTableRenameColumnSQL() : array;
1364 1365 1366 1367

    /**
     * @group DBAL-1016
     */
1368
    public function testQuotesTableIdentifiersInAlterTableSQL() : void
1369 1370 1371 1372 1373 1374 1375 1376
    {
        $table = new Table('"foo"');
        $table->addColumn('id', 'integer');
        $table->addColumn('fk', 'integer');
        $table->addColumn('fk2', 'integer');
        $table->addColumn('fk3', 'integer');
        $table->addColumn('bar', 'integer');
        $table->addColumn('baz', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
1377 1378
        $table->addForeignKeyConstraint('fk_table', ['fk'], ['id'], [], 'fk1');
        $table->addForeignKeyConstraint('fk_table', ['fk2'], ['id'], [], 'fk2');
1379

Sergei Morozov's avatar
Sergei Morozov committed
1380 1381 1382 1383
        $tableDiff                        = new TableDiff('"foo"');
        $tableDiff->fromTable             = $table;
        $tableDiff->newName               = 'table';
        $tableDiff->addedColumns['bloo']  = new Column('bloo', Type::getType('integer'));
1384 1385
        $tableDiff->changedColumns['bar'] = new ColumnDiff(
            'bar',
Sergei Morozov's avatar
Sergei Morozov committed
1386 1387
            new Column('bar', Type::getType('integer'), ['notnull' => false]),
            ['notnull'],
1388 1389
            $table->getColumn('bar')
        );
Sergei Morozov's avatar
Sergei Morozov committed
1390
        $tableDiff->renamedColumns['id']  = new Column('war', Type::getType('integer'));
1391
        $tableDiff->removedColumns['baz'] = new Column('baz', Type::getType('integer'));
Sergei Morozov's avatar
Sergei Morozov committed
1392 1393 1394
        $tableDiff->addedForeignKeys[]    = new ForeignKeyConstraint(['fk3'], 'fk_table', ['id'], 'fk_add');
        $tableDiff->changedForeignKeys[]  = new ForeignKeyConstraint(['fk2'], 'fk_table2', ['id'], 'fk2');
        $tableDiff->removedForeignKeys[]  = new ForeignKeyConstraint(['fk'], 'fk_table', ['id'], 'fk1');
1395

1396
        self::assertSame(
1397
            $this->getQuotesTableIdentifiersInAlterTableSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1398
            $this->platform->getAlterTableSQL($tableDiff)
1399 1400 1401 1402
        );
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1403
     * @return string[]
1404
     */
1405
    abstract protected function getQuotesTableIdentifiersInAlterTableSQL() : array;
1406 1407 1408 1409

    /**
     * @group DBAL-1090
     */
1410
    public function testAlterStringToFixedString() : void
1411 1412
    {
        $table = new Table('mytable');
Sergei Morozov's avatar
Sergei Morozov committed
1413
        $table->addColumn('name', 'string', ['length' => 2]);
1414

Sergei Morozov's avatar
Sergei Morozov committed
1415
        $tableDiff            = new TableDiff('mytable');
1416 1417
        $tableDiff->fromTable = $table;

Sergei Morozov's avatar
Sergei Morozov committed
1418 1419 1420 1421 1422 1423
        $tableDiff->changedColumns['name'] = new ColumnDiff(
            'name',
            new Column(
                'name',
                Type::getType('string'),
                ['fixed' => true, 'length' => 2]
1424
            ),
Sergei Morozov's avatar
Sergei Morozov committed
1425
            ['fixed']
1426 1427
        );

Sergei Morozov's avatar
Sergei Morozov committed
1428
        $sql = $this->platform->getAlterTableSQL($tableDiff);
1429 1430 1431

        $expectedSql = $this->getAlterStringToFixedStringSQL();

1432
        self::assertEquals($expectedSql, $sql);
1433 1434 1435
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1436
     * @return string[]
1437
     */
1438
    abstract protected function getAlterStringToFixedStringSQL() : array;
1439 1440 1441 1442

    /**
     * @group DBAL-1062
     */
1443
    public function testGeneratesAlterTableRenameIndexUsedByForeignKeySQL() : void
1444 1445 1446
    {
        $foreignTable = new Table('foreign_table');
        $foreignTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
1447
        $foreignTable->setPrimaryKey(['id']);
1448 1449 1450 1451 1452

        $primaryTable = new Table('mytable');
        $primaryTable->addColumn('foo', 'integer');
        $primaryTable->addColumn('bar', 'integer');
        $primaryTable->addColumn('baz', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
1453 1454 1455 1456
        $primaryTable->addIndex(['foo'], 'idx_foo');
        $primaryTable->addIndex(['bar'], 'idx_bar');
        $primaryTable->addForeignKeyConstraint($foreignTable, ['foo'], ['id'], [], 'fk_foo');
        $primaryTable->addForeignKeyConstraint($foreignTable, ['bar'], ['id'], [], 'fk_bar');
1457

Sergei Morozov's avatar
Sergei Morozov committed
1458 1459 1460
        $tableDiff                            = new TableDiff('mytable');
        $tableDiff->fromTable                 = $primaryTable;
        $tableDiff->renamedIndexes['idx_foo'] = new Index('idx_foo_renamed', ['foo']);
1461

1462
        self::assertSame(
1463
            $this->getGeneratesAlterTableRenameIndexUsedByForeignKeySQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1464
            $this->platform->getAlterTableSQL($tableDiff)
1465 1466 1467 1468
        );
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1469
     * @return string[]
1470
     */
1471
    abstract protected function getGeneratesAlterTableRenameIndexUsedByForeignKeySQL() : array;
1472 1473

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1474 1475
     * @param mixed[] $column
     *
1476 1477 1478
     * @group DBAL-1082
     * @dataProvider getGeneratesDecimalTypeDeclarationSQL
     */
1479
    public function testGeneratesDecimalTypeDeclarationSQL(array $column, string $expectedSql) : void
1480
    {
Sergei Morozov's avatar
Sergei Morozov committed
1481
        self::assertSame($expectedSql, $this->platform->getDecimalTypeDeclarationSQL($column));
1482 1483 1484
    }

    /**
1485
     * @return mixed[][]
1486
     */
1487
    public static function getGeneratesDecimalTypeDeclarationSQL() : iterable
1488
    {
Sergei Morozov's avatar
Sergei Morozov committed
1489 1490 1491 1492 1493 1494 1495 1496
        return [
            [[], 'NUMERIC(10, 0)'],
            [['unsigned' => true], 'NUMERIC(10, 0)'],
            [['unsigned' => false], 'NUMERIC(10, 0)'],
            [['precision' => 5], 'NUMERIC(5, 0)'],
            [['scale' => 5], 'NUMERIC(10, 5)'],
            [['precision' => 8, 'scale' => 2], 'NUMERIC(8, 2)'],
        ];
1497 1498 1499
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1500 1501
     * @param mixed[] $column
     *
1502 1503 1504
     * @group DBAL-1082
     * @dataProvider getGeneratesFloatDeclarationSQL
     */
1505
    public function testGeneratesFloatDeclarationSQL(array $column, string $expectedSql) : void
1506
    {
Sergei Morozov's avatar
Sergei Morozov committed
1507
        self::assertSame($expectedSql, $this->platform->getFloatDeclarationSQL($column));
1508 1509 1510
    }

    /**
1511
     * @return mixed[][]
1512
     */
1513
    public static function getGeneratesFloatDeclarationSQL() : iterable
1514
    {
Sergei Morozov's avatar
Sergei Morozov committed
1515 1516 1517 1518 1519 1520 1521 1522
        return [
            [[], 'DOUBLE PRECISION'],
            [['unsigned' => true], 'DOUBLE PRECISION'],
            [['unsigned' => false], 'DOUBLE PRECISION'],
            [['precision' => 5], 'DOUBLE PRECISION'],
            [['scale' => 5], 'DOUBLE PRECISION'],
            [['precision' => 8, 'scale' => 2], 'DOUBLE PRECISION'],
        ];
1523
    }
1524 1525 1526 1527 1528

    public function testItEscapesStringsForLike() : void
    {
        self::assertSame(
            '\_25\% off\_ your next purchase \\\\o/',
Sergei Morozov's avatar
Sergei Morozov committed
1529
            $this->platform->escapeStringForLike('_25% off_ your next purchase \o/', '\\')
1530 1531
        );
    }
1532 1533 1534 1535 1536 1537 1538

    public function testZeroOffsetWithoutLimitIsIgnored() : void
    {
        $query = 'SELECT * FROM user';

        self::assertSame(
            $query,
Sergei Morozov's avatar
Sergei Morozov committed
1539
            $this->platform->modifyLimitQuery($query, null, 0)
1540 1541
        );
    }
1542
}