AbstractPlatformTestCase.php 50.6 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\Types\Type;
18
use PHPUnit\Framework\TestCase;
19

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
    /**
     * @return mixed[]
     */
138
    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
    /**
     * @return string[]
     */
191
    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
    {
Sergei Morozov's avatar
Sergei Morozov committed
217 218 219
        $where       = 'test IS NULL AND test2 IS NOT NULL';
        $indexDef    = new Index('name', ['test', 'test2'], false, false, [], ['where' => $where]);
        $uniqueIndex = new Index('name', ['test', 'test2'], true, false, [], ['where' => $where]);
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
        }

Sergei Morozov's avatar
Sergei Morozov committed
229 230
        $actuals[] = $this->platform->getUniqueConstraintDeclarationSQL('name', $uniqueIndex);
        $actuals[] = $this->platform->getCreateIndexSQL($indexDef, 'table');
231 232

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

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

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

249
    abstract protected function getGenerateForeignKeySql(): string;
250

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

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

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

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

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

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

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

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

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

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

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

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

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

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

327
        $table = new Table('mytable');
Sergei Morozov's avatar
Sergei Morozov committed
328
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
329 330 331
        $table->addColumn('foo', 'integer');
        $table->addColumn('bar', 'string');
        $table->addColumn('bloo', 'boolean');
Sergei Morozov's avatar
Sergei Morozov committed
332 333 334 335 336 337 338 339 340 341 342 343 344
        $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']
345
            ),
Sergei Morozov's avatar
Sergei Morozov committed
346
            ['type', 'notnull', 'default']
347
        );
Sergei Morozov's avatar
Sergei Morozov committed
348 349 350 351 352 353
        $tableDiff->changedColumns['bloo'] = new ColumnDiff(
            'bloo',
            new Column(
                'bloo',
                Type::getType('boolean'),
                ['default' => false]
354
            ),
Sergei Morozov's avatar
Sergei Morozov committed
355
            ['type', 'notnull', 'default']
356
        );
357

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

360
        self::assertEquals($expectedSql, $sql);
361
    }
362

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

369
    public function testGetCreateTableSqlDispatchEvent(): void
370
    {
371
        $listenerMock = $this->createMock(GetCreateTableSqlDispatchEventListener::class);
372
        $listenerMock
373
            ->expects(self::once())
374
            ->method('onSchemaCreateTable');
375
        $listenerMock
376
            ->expects(self::exactly(2))
377 378 379
            ->method('onSchemaCreateTableColumn');

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

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

384
        $table = new Table('test');
Sergei Morozov's avatar
Sergei Morozov committed
385 386
        $table->addColumn('foo', 'string', ['notnull' => false, 'length' => 255]);
        $table->addColumn('bar', 'string', ['notnull' => false, 'length' => 255]);
387

Sergei Morozov's avatar
Sergei Morozov committed
388
        $this->platform->getCreateTableSQL($table);
389 390
    }

391
    public function testGetDropTableSqlDispatchEvent(): void
392
    {
393
        $listenerMock = $this->createMock(GetDropTableSqlDispatchEventListener::class);
394
        $listenerMock
395
            ->expects(self::once())
396 397 398
            ->method('onSchemaDropTable');

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

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

Sergei Morozov's avatar
Sergei Morozov committed
403
        $this->platform->getDropTableSQL('TABLE');
404
    }
405

406
    public function testGetAlterTableSqlDispatchEvent(): void
407
    {
408
        $listenerMock = $this->createMock(GetAlterTableSqlDispatchEventListener::class);
409
        $listenerMock
410
            ->expects(self::once())
411
            ->method('onSchemaAlterTable');
412
        $listenerMock
413
            ->expects(self::once())
jsor's avatar
jsor committed
414
            ->method('onSchemaAlterTableAddColumn');
415
        $listenerMock
416
            ->expects(self::once())
jsor's avatar
jsor committed
417
            ->method('onSchemaAlterTableRemoveColumn');
418
        $listenerMock
419
            ->expects(self::once())
jsor's avatar
jsor committed
420
            ->method('onSchemaAlterTableChangeColumn');
421
        $listenerMock
422
            ->expects(self::once())
jsor's avatar
jsor committed
423
            ->method('onSchemaAlterTableRenameColumn');
424 425

        $eventManager = new EventManager();
Sergei Morozov's avatar
Sergei Morozov committed
426
        $events       = [
427
            Events::onSchemaAlterTable,
jsor's avatar
jsor committed
428 429 430
            Events::onSchemaAlterTableAddColumn,
            Events::onSchemaAlterTableRemoveColumn,
            Events::onSchemaAlterTableChangeColumn,
Sergei Morozov's avatar
Sergei Morozov committed
431 432
            Events::onSchemaAlterTableRenameColumn,
        ];
433 434
        $eventManager->addEventListener($events, $listenerMock);

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

437 438 439 440 441
        $table = new Table('mytable');
        $table->addColumn('removed', 'integer');
        $table->addColumn('changed', 'integer');
        $table->addColumn('renamed', 'integer');

Sergei Morozov's avatar
Sergei Morozov committed
442 443 444 445 446 447 448 449 450 451
        $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'),
                []
452
            ),
Sergei Morozov's avatar
Sergei Morozov committed
453
            []
454
        );
Sergei Morozov's avatar
Sergei Morozov committed
455
        $tableDiff->renamedColumns['renamed'] = new Column('renamed2', Type::getType('integer'), []);
456

Sergei Morozov's avatar
Sergei Morozov committed
457
        $this->platform->getAlterTableSQL($tableDiff);
458
    }
459

460 461 462
    /**
     * @group DBAL-42
     */
463
    public function testCreateTableColumnComments(): void
464
    {
465
        $table = new Table('test');
Sergei Morozov's avatar
Sergei Morozov committed
466 467
        $table->addColumn('id', 'integer', ['comment' => 'This is a comment']);
        $table->setPrimaryKey(['id']);
468

Sergei Morozov's avatar
Sergei Morozov committed
469
        self::assertEquals($this->getCreateTableColumnCommentsSQL(), $this->platform->getCreateTableSQL($table));
470 471 472 473 474
    }

    /**
     * @group DBAL-42
     */
475
    public function testAlterTableColumnComments(): void
476
    {
Sergei Morozov's avatar
Sergei Morozov committed
477 478 479 480 481 482 483
        $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')
484
            ),
Sergei Morozov's avatar
Sergei Morozov committed
485
            ['comment']
486
        );
Sergei Morozov's avatar
Sergei Morozov committed
487 488 489 490 491 492
        $tableDiff->changedColumns['bar'] = new ColumnDiff(
            'bar',
            new Column(
                'baz',
                Type::getType('string'),
                ['comment' => 'B comment']
493
            ),
Sergei Morozov's avatar
Sergei Morozov committed
494
            ['comment']
495 496
        );

Sergei Morozov's avatar
Sergei Morozov committed
497
        self::assertEquals($this->getAlterTableColumnCommentsSQL(), $this->platform->getAlterTableSQL($tableDiff));
498 499
    }

500
    public function testCreateTableColumnTypeComments(): void
501
    {
502
        $table = new Table('test');
503 504
        $table->addColumn('id', 'integer');
        $table->addColumn('data', 'array');
Sergei Morozov's avatar
Sergei Morozov committed
505
        $table->setPrimaryKey(['id']);
506

Sergei Morozov's avatar
Sergei Morozov committed
507
        self::assertEquals($this->getCreateTableColumnTypeCommentsSQL(), $this->platform->getCreateTableSQL($table));
508 509
    }

510 511 512
    /**
     * @return string[]
     */
513
    public function getCreateTableColumnCommentsSQL(): array
514
    {
515
        self::markTestSkipped('Platform does not support Column comments.');
516 517
    }

518 519 520
    /**
     * @return string[]
     */
521
    public function getAlterTableColumnCommentsSQL(): array
522
    {
523
        self::markTestSkipped('Platform does not support Column comments.');
524
    }
525

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

534
    public function testGetDefaultValueDeclarationSQL(): void
535 536
    {
        // non-timestamp value will get single quotes
Sergei Morozov's avatar
Sergei Morozov committed
537
        $field = [
538
            'type' => Type::getType('string'),
Sergei Morozov's avatar
Sergei Morozov committed
539 540
            'default' => 'non_timestamp',
        ];
541

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

545 546 547
    /**
     * @group 2859
     */
548
    public function testGetDefaultValueDeclarationSQLDateTime(): void
549 550
    {
        // timestamps on datetime types should not be quoted
551 552 553
        foreach (['datetime', 'datetimetz', 'datetime_immutable', 'datetimetz_immutable'] as $type) {
            $field = [
                'type'    => Type::getType($type),
Sergei Morozov's avatar
Sergei Morozov committed
554
                'default' => $this->platform->getCurrentTimestampSQL(),
555
            ];
556

557
            self::assertSame(
Sergei Morozov's avatar
Sergei Morozov committed
558 559
                ' DEFAULT ' . $this->platform->getCurrentTimestampSQL(),
                $this->platform->getDefaultValueDeclarationSQL($field)
560 561 562 563
            );
        }
    }

564
    public function testGetDefaultValueDeclarationSQLForIntegerTypes(): void
565
    {
Sergei Morozov's avatar
Sergei Morozov committed
566 567
        foreach (['bigint', 'integer', 'smallint'] as $type) {
            $field = [
568
                'type'    => Type::getType($type),
Sergei Morozov's avatar
Sergei Morozov committed
569 570
                'default' => 1,
            ];
571

572
            self::assertEquals(
573
                ' DEFAULT 1',
Sergei Morozov's avatar
Sergei Morozov committed
574
                $this->platform->getDefaultValueDeclarationSQL($field)
575 576 577 578
            );
        }
    }

579 580 581
    /**
     * @group 2859
     */
582
    public function testGetDefaultValueDeclarationSQLForDateType(): void
583
    {
Sergei Morozov's avatar
Sergei Morozov committed
584
        $currentDateSql = $this->platform->getCurrentDateSQL();
585 586 587 588 589 590 591 592
        foreach (['date', 'date_immutable'] as $type) {
            $field = [
                'type'    => Type::getType($type),
                'default' => $currentDateSql,
            ];

            self::assertSame(
                ' DEFAULT ' . $currentDateSql,
Sergei Morozov's avatar
Sergei Morozov committed
593
                $this->platform->getDefaultValueDeclarationSQL($field)
594 595
            );
        }
596 597
    }

598 599 600
    /**
     * @group DBAL-45
     */
601
    public function testKeywordList(): void
602
    {
Sergei Morozov's avatar
Sergei Morozov committed
603 604
        $keywordList = $this->platform->getReservedKeywordsList();
        self::assertInstanceOf(KeywordList::class, $keywordList);
605

606
        self::assertTrue($keywordList->isKeyword('table'));
607
    }
608 609 610 611

    /**
     * @group DBAL-374
     */
612
    public function testQuotedColumnInPrimaryKeyPropagation(): void
613 614
    {
        $table = new Table('`quoted`');
615
        $table->addColumn('create', 'string');
Sergei Morozov's avatar
Sergei Morozov committed
616
        $table->setPrimaryKey(['create']);
617

Sergei Morozov's avatar
Sergei Morozov committed
618
        $sql = $this->platform->getCreateTableSQL($table);
619
        self::assertEquals($this->getQuotedColumnInPrimaryKeySQL(), $sql);
620 621
    }

622 623 624
    /**
     * @return string[]
     */
625
    abstract protected function getQuotedColumnInPrimaryKeySQL(): array;
626

627 628 629
    /**
     * @return string[]
     */
630
    abstract protected function getQuotedColumnInIndexSQL(): array;
631

632 633 634
    /**
     * @return string[]
     */
635
    abstract protected function getQuotedNameInIndexSQL(): array;
636

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

    /**
     * @group DBAL-374
     */
645
    public function testQuotedColumnInIndexPropagation(): void
646 647
    {
        $table = new Table('`quoted`');
648
        $table->addColumn('create', 'string');
Sergei Morozov's avatar
Sergei Morozov committed
649
        $table->addIndex(['create']);
650

Sergei Morozov's avatar
Sergei Morozov committed
651
        $sql = $this->platform->getCreateTableSQL($table);
652
        self::assertEquals($this->getQuotedColumnInIndexSQL(), $sql);
653
    }
654

655
    public function testQuotedNameInIndexSQL(): void
Markus Fasselt's avatar
Markus Fasselt committed
656 657 658
    {
        $table = new Table('test');
        $table->addColumn('column1', 'string');
Sergei Morozov's avatar
Sergei Morozov committed
659
        $table->addIndex(['column1'], '`key`');
Markus Fasselt's avatar
Markus Fasselt committed
660

Sergei Morozov's avatar
Sergei Morozov committed
661
        $sql = $this->platform->getCreateTableSQL($table);
662
        self::assertEquals($this->getQuotedNameInIndexSQL(), $sql);
Markus Fasselt's avatar
Markus Fasselt committed
663 664
    }

665 666 667
    /**
     * @group DBAL-374
     */
668
    public function testQuotedColumnInForeignKeyPropagation(): void
669 670 671 672
    {
        $table = new Table('`quoted`');
        $table->addColumn('create', 'string');
        $table->addColumn('foo', 'string');
673
        $table->addColumn('`bar`', 'string');
674

675
        // Foreign table with reserved keyword as name (needs quotation).
676
        $foreignTable = new Table('foreign');
677 678 679
        $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).
680

Sergei Morozov's avatar
Sergei Morozov committed
681
        $table->addForeignKeyConstraint($foreignTable, ['create', 'foo', '`bar`'], ['create', 'bar', '`foo-bar`'], [], 'FK_WITH_RESERVED_KEYWORD');
682 683 684 685 686 687 688

        // 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
689
        $table->addForeignKeyConstraint($foreignTable, ['create', 'foo', '`bar`'], ['create', 'bar', '`foo-bar`'], [], 'FK_WITH_NON_RESERVED_KEYWORD');
690 691 692 693 694 695 696

        // 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
697
        $table->addForeignKeyConstraint($foreignTable, ['create', 'foo', '`bar`'], ['create', 'bar', '`foo-bar`'], [], 'FK_WITH_INTENDED_QUOTATION');
698

Sergei Morozov's avatar
Sergei Morozov committed
699
        $sql = $this->platform->getCreateTableSQL($table, AbstractPlatform::CREATE_FOREIGNKEYS);
700
        self::assertEquals($this->getQuotedColumnInForeignKeySQL(), $sql);
701
    }
702

703 704 705
    /**
     * @group DBAL-1051
     */
706
    public function testQuotesReservedKeywordInUniqueConstraintDeclarationSQL(): void
707
    {
Sergei Morozov's avatar
Sergei Morozov committed
708
        $index = new Index('select', ['foo'], true);
709

710
        self::assertSame(
711
            $this->getQuotesReservedKeywordInUniqueConstraintDeclarationSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
712
            $this->platform->getUniqueConstraintDeclarationSQL('select', $index)
713 714 715
        );
    }

716
    abstract protected function getQuotesReservedKeywordInUniqueConstraintDeclarationSQL(): string;
717

718 719 720
    /**
     * @group DBAL-2270
     */
721
    public function testQuotesReservedKeywordInTruncateTableSQL(): void
722
    {
723
        self::assertSame(
724
            $this->getQuotesReservedKeywordInTruncateTableSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
725
            $this->platform->getTruncateTableSQL('select')
726 727 728
        );
    }

729
    abstract protected function getQuotesReservedKeywordInTruncateTableSQL(): string;
730

731 732 733
    /**
     * @group DBAL-1051
     */
734
    public function testQuotesReservedKeywordInIndexDeclarationSQL(): void
735
    {
Sergei Morozov's avatar
Sergei Morozov committed
736
        $index = new Index('select', ['foo']);
737 738

        if (! $this->supportsInlineIndexDeclaration()) {
Sergei Morozov's avatar
Sergei Morozov committed
739
            $this->expectException(DBALException::class);
740 741
        }

742
        self::assertSame(
743
            $this->getQuotesReservedKeywordInIndexDeclarationSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
744
            $this->platform->getIndexDeclarationSQL('select', $index)
745 746 747
        );
    }

748
    abstract protected function getQuotesReservedKeywordInIndexDeclarationSQL(): string;
749

750
    protected function supportsInlineIndexDeclaration(): bool
751 752 753 754
    {
        return true;
    }

755
    public function testSupportsCommentOnStatement(): void
756
    {
Sergei Morozov's avatar
Sergei Morozov committed
757
        self::assertSame($this->supportsCommentOnStatement(), $this->platform->supportsCommentOnStatement());
758 759
    }

760
    protected function supportsCommentOnStatement(): bool
761 762 763 764
    {
        return false;
    }

765
    public function testGetCreateSchemaSQL(): void
766
    {
767 768
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
769
        $this->platform->getCreateSchemaSQL('schema');
770 771
    }

772 773 774
    /**
     * @group DBAL-585
     */
775
    public function testAlterTableChangeQuotedColumn(): void
776
    {
Sergei Morozov's avatar
Sergei Morozov committed
777 778 779 780 781 782 783
        $tableDiff                        = new TableDiff('mytable');
        $tableDiff->fromTable             = new Table('mytable');
        $tableDiff->changedColumns['foo'] = new ColumnDiff(
            'select',
            new Column(
                'select',
                Type::getType('string')
784
            ),
Sergei Morozov's avatar
Sergei Morozov committed
785
            ['type']
786 787
        );

788
        self::assertStringContainsString(
Sergei Morozov's avatar
Sergei Morozov committed
789 790
            $this->platform->quoteIdentifier('select'),
            implode(';', $this->platform->getAlterTableSQL($tableDiff))
791 792
        );
    }
Benjamin Eberlei's avatar
Benjamin Eberlei committed
793

794 795 796
    /**
     * @group DBAL-563
     */
797
    public function testUsesSequenceEmulatedIdentityColumns(): void
798
    {
Sergei Morozov's avatar
Sergei Morozov committed
799
        self::assertFalse($this->platform->usesSequenceEmulatedIdentityColumns());
800 801 802 803 804
    }

    /**
     * @group DBAL-563
     */
805
    public function testReturnsIdentitySequenceName(): void
806
    {
807 808
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
809
        $this->platform->getIdentitySequenceName('mytable', 'mycolumn');
810
    }
Steve Müller's avatar
Steve Müller committed
811

812
    public function testReturnsBinaryDefaultLength(): void
Steve Müller's avatar
Steve Müller committed
813
    {
Sergei Morozov's avatar
Sergei Morozov committed
814
        self::assertSame($this->getBinaryDefaultLength(), $this->platform->getBinaryDefaultLength());
Steve Müller's avatar
Steve Müller committed
815 816
    }

817
    protected function getBinaryDefaultLength(): int
Steve Müller's avatar
Steve Müller committed
818 819 820 821
    {
        return 255;
    }

822
    public function testReturnsBinaryMaxLength(): void
Steve Müller's avatar
Steve Müller committed
823
    {
Sergei Morozov's avatar
Sergei Morozov committed
824
        self::assertSame($this->getBinaryMaxLength(), $this->platform->getBinaryMaxLength());
Steve Müller's avatar
Steve Müller committed
825 826
    }

827
    protected function getBinaryMaxLength(): int
Steve Müller's avatar
Steve Müller committed
828 829 830 831
    {
        return 4000;
    }

832
    public function testReturnsBinaryTypeDeclarationSQL(): void
Steve Müller's avatar
Steve Müller committed
833
    {
834 835
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
836
        $this->platform->getBinaryTypeDeclarationSQL([]);
Steve Müller's avatar
Steve Müller committed
837
    }
838

839
    public function testReturnsBinaryTypeLongerThanMaxDeclarationSQL(): void
840 841 842 843
    {
        $this->markTestSkipped('Not applicable to the platform');
    }

844 845 846
    /**
     * @group DBAL-553
     */
847
    public function hasNativeJsonType(): void
848
    {
Sergei Morozov's avatar
Sergei Morozov committed
849
        self::assertFalse($this->platform->hasNativeJsonType());
850 851 852 853 854
    }

    /**
     * @group DBAL-553
     */
855
    public function testReturnsJsonTypeDeclarationSQL(): void
856
    {
Sergei Morozov's avatar
Sergei Morozov committed
857
        $column = [
858 859
            'length'  => 666,
            'notnull' => true,
860
            'type'    => Type::getType('json'),
Sergei Morozov's avatar
Sergei Morozov committed
861
        ];
862

863
        self::assertSame(
Sergei Morozov's avatar
Sergei Morozov committed
864 865
            $this->platform->getClobTypeDeclarationSQL($column),
            $this->platform->getJsonTypeDeclarationSQL($column)
866 867
        );
    }
868 869 870 871

    /**
     * @group DBAL-234
     */
872
    public function testAlterTableRenameIndex(): void
873
    {
Sergei Morozov's avatar
Sergei Morozov committed
874
        $tableDiff            = new TableDiff('mytable');
875 876
        $tableDiff->fromTable = new Table('mytable');
        $tableDiff->fromTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
877 878 879 880
        $tableDiff->fromTable->setPrimaryKey(['id']);
        $tableDiff->renamedIndexes = [
            'idx_foo' => new Index('idx_bar', ['id']),
        ];
881

882
        self::assertSame(
883
            $this->getAlterTableRenameIndexSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
884
            $this->platform->getAlterTableSQL($tableDiff)
885 886 887 888
        );
    }

    /**
889 890
     * @return string[]
     *
891 892
     * @group DBAL-234
     */
893
    protected function getAlterTableRenameIndexSQL(): array
894
    {
Sergei Morozov's avatar
Sergei Morozov committed
895
        return [
896 897
            'DROP INDEX idx_foo',
            'CREATE INDEX idx_bar ON mytable (id)',
Sergei Morozov's avatar
Sergei Morozov committed
898
        ];
899 900 901 902 903
    }

    /**
     * @group DBAL-234
     */
904
    public function testQuotesAlterTableRenameIndex(): void
905
    {
Sergei Morozov's avatar
Sergei Morozov committed
906
        $tableDiff            = new TableDiff('table');
907 908
        $tableDiff->fromTable = new Table('table');
        $tableDiff->fromTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
909 910 911 912 913
        $tableDiff->fromTable->setPrimaryKey(['id']);
        $tableDiff->renamedIndexes = [
            'create' => new Index('select', ['id']),
            '`foo`'  => new Index('`bar`', ['id']),
        ];
914

915
        self::assertSame(
916
            $this->getQuotedAlterTableRenameIndexSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
917
            $this->platform->getAlterTableSQL($tableDiff)
918 919 920 921
        );
    }

    /**
922 923
     * @return string[]
     *
924 925
     * @group DBAL-234
     */
926
    protected function getQuotedAlterTableRenameIndexSQL(): array
927
    {
Sergei Morozov's avatar
Sergei Morozov committed
928
        return [
929 930 931 932
            '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
933
        ];
934
    }
935 936 937 938

    /**
     * @group DBAL-835
     */
939
    public function testQuotesAlterTableRenameColumn(): void
940 941 942
    {
        $fromTable = new Table('mytable');

Sergei Morozov's avatar
Sergei Morozov committed
943 944 945
        $fromTable->addColumn('unquoted1', 'integer', ['comment' => 'Unquoted 1']);
        $fromTable->addColumn('unquoted2', 'integer', ['comment' => 'Unquoted 2']);
        $fromTable->addColumn('unquoted3', 'integer', ['comment' => 'Unquoted 3']);
946

Sergei Morozov's avatar
Sergei Morozov committed
947 948 949
        $fromTable->addColumn('create', 'integer', ['comment' => 'Reserved keyword 1']);
        $fromTable->addColumn('table', 'integer', ['comment' => 'Reserved keyword 2']);
        $fromTable->addColumn('select', 'integer', ['comment' => 'Reserved keyword 3']);
950

Sergei Morozov's avatar
Sergei Morozov committed
951 952 953
        $fromTable->addColumn('`quoted1`', 'integer', ['comment' => 'Quoted 1']);
        $fromTable->addColumn('`quoted2`', 'integer', ['comment' => 'Quoted 2']);
        $fromTable->addColumn('`quoted3`', 'integer', ['comment' => 'Quoted 3']);
954 955 956

        $toTable = new Table('mytable');

Sergei Morozov's avatar
Sergei Morozov committed
957 958 959
        $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
960

Sergei Morozov's avatar
Sergei Morozov committed
961 962 963
        $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
964

Sergei Morozov's avatar
Sergei Morozov committed
965 966 967
        $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
968 969 970

        $comparator = new Comparator();

971
        self::assertEquals(
972
            $this->getQuotedAlterTableRenameColumnSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
973
            $this->platform->getAlterTableSQL($comparator->diffTable($fromTable, $toTable))
974 975 976 977 978 979
        );
    }

    /**
     * Returns SQL statements for {@link testQuotesAlterTableRenameColumn}.
     *
Sergei Morozov's avatar
Sergei Morozov committed
980
     * @return string[]
981 982 983
     *
     * @group DBAL-835
     */
984
    abstract protected function getQuotedAlterTableRenameColumnSQL(): array;
985

986 987 988
    /**
     * @group DBAL-835
     */
989
    public function testQuotesAlterTableChangeColumnLength(): void
990 991 992
    {
        $fromTable = new Table('mytable');

Sergei Morozov's avatar
Sergei Morozov committed
993 994 995
        $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]);
996

Sergei Morozov's avatar
Sergei Morozov committed
997 998 999
        $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]);
1000 1001 1002

        $toTable = new Table('mytable');

Sergei Morozov's avatar
Sergei Morozov committed
1003 1004 1005
        $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]);
1006

Sergei Morozov's avatar
Sergei Morozov committed
1007 1008 1009
        $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]);
1010 1011 1012

        $comparator = new Comparator();

1013
        self::assertEquals(
1014
            $this->getQuotedAlterTableChangeColumnLengthSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1015
            $this->platform->getAlterTableSQL($comparator->diffTable($fromTable, $toTable))
1016 1017 1018 1019 1020 1021
        );
    }

    /**
     * Returns SQL statements for {@link testQuotesAlterTableChangeColumnLength}.
     *
Sergei Morozov's avatar
Sergei Morozov committed
1022
     * @return string[]
1023 1024 1025
     *
     * @group DBAL-835
     */
1026
    abstract protected function getQuotedAlterTableChangeColumnLengthSQL(): array;
1027

1028 1029 1030
    /**
     * @group DBAL-807
     */
1031
    public function testAlterTableRenameIndexInSchema(): void
1032
    {
Sergei Morozov's avatar
Sergei Morozov committed
1033
        $tableDiff            = new TableDiff('myschema.mytable');
1034 1035
        $tableDiff->fromTable = new Table('myschema.mytable');
        $tableDiff->fromTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
1036 1037 1038 1039
        $tableDiff->fromTable->setPrimaryKey(['id']);
        $tableDiff->renamedIndexes = [
            'idx_foo' => new Index('idx_bar', ['id']),
        ];
1040

1041
        self::assertSame(
1042
            $this->getAlterTableRenameIndexInSchemaSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1043
            $this->platform->getAlterTableSQL($tableDiff)
1044 1045 1046 1047
        );
    }

    /**
1048 1049
     * @return string[]
     *
1050 1051
     * @group DBAL-807
     */
1052
    protected function getAlterTableRenameIndexInSchemaSQL(): array
1053
    {
Sergei Morozov's avatar
Sergei Morozov committed
1054
        return [
1055 1056
            'DROP INDEX idx_foo',
            'CREATE INDEX idx_bar ON myschema.mytable (id)',
Sergei Morozov's avatar
Sergei Morozov committed
1057
        ];
1058 1059 1060 1061 1062
    }

    /**
     * @group DBAL-807
     */
1063
    public function testQuotesAlterTableRenameIndexInSchema(): void
1064
    {
Sergei Morozov's avatar
Sergei Morozov committed
1065
        $tableDiff            = new TableDiff('`schema`.table');
1066 1067
        $tableDiff->fromTable = new Table('`schema`.table');
        $tableDiff->fromTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
1068 1069 1070 1071 1072
        $tableDiff->fromTable->setPrimaryKey(['id']);
        $tableDiff->renamedIndexes = [
            'create' => new Index('select', ['id']),
            '`foo`'  => new Index('`bar`', ['id']),
        ];
1073

1074
        self::assertSame(
1075
            $this->getQuotedAlterTableRenameIndexInSchemaSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1076
            $this->platform->getAlterTableSQL($tableDiff)
1077 1078 1079 1080
        );
    }

    /**
1081 1082
     * @return string[]
     *
1083 1084
     * @group DBAL-234
     */
1085
    protected function getQuotedAlterTableRenameIndexInSchemaSQL(): array
1086
    {
Sergei Morozov's avatar
Sergei Morozov committed
1087
        return [
1088 1089 1090 1091
            '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
1092
        ];
1093
    }
1094

1095 1096 1097
    /**
     * @group DBAL-1237
     */
1098
    public function testQuotesDropForeignKeySQL(): void
1099
    {
1100
        if (! $this->platform->supportsCreateDropForeignKeyConstraints()) {
1101
            self::markTestSkipped(
1102
                sprintf('%s does not support modifying foreign key constraints.', get_class($this->platform))
1103 1104 1105
            );
        }

Sergei Morozov's avatar
Sergei Morozov committed
1106 1107
        $tableName      = 'table';
        $table          = new Table($tableName);
1108
        $foreignKeyName = 'select';
Sergei Morozov's avatar
Sergei Morozov committed
1109 1110
        $foreignKey     = new ForeignKeyConstraint([], 'foo', [], 'select');
        $expectedSql    = $this->getQuotesDropForeignKeySQL();
1111

Sergei Morozov's avatar
Sergei Morozov committed
1112 1113
        self::assertSame($expectedSql, $this->platform->getDropForeignKeySQL($foreignKeyName, $tableName));
        self::assertSame($expectedSql, $this->platform->getDropForeignKeySQL($foreignKey, $table));
1114 1115
    }

1116
    protected function getQuotesDropForeignKeySQL(): string
1117 1118 1119 1120 1121 1122 1123
    {
        return 'ALTER TABLE "table" DROP FOREIGN KEY "select"';
    }

    /**
     * @group DBAL-1237
     */
1124
    public function testQuotesDropConstraintSQL(): void
1125
    {
Sergei Morozov's avatar
Sergei Morozov committed
1126 1127
        $tableName      = 'table';
        $table          = new Table($tableName);
1128
        $constraintName = 'select';
Sergei Morozov's avatar
Sergei Morozov committed
1129 1130
        $constraint     = new ForeignKeyConstraint([], 'foo', [], 'select');
        $expectedSql    = $this->getQuotesDropConstraintSQL();
1131

Sergei Morozov's avatar
Sergei Morozov committed
1132 1133
        self::assertSame($expectedSql, $this->platform->getDropConstraintSQL($constraintName, $tableName));
        self::assertSame($expectedSql, $this->platform->getDropConstraintSQL($constraint, $table));
1134 1135
    }

1136
    protected function getQuotesDropConstraintSQL(): string
1137 1138 1139 1140
    {
        return 'ALTER TABLE "table" DROP CONSTRAINT "select"';
    }

1141
    protected function getStringLiteralQuoteCharacter(): string
1142 1143 1144 1145
    {
        return "'";
    }

1146
    public function testGetStringLiteralQuoteCharacter(): void
1147
    {
Sergei Morozov's avatar
Sergei Morozov committed
1148
        self::assertSame($this->getStringLiteralQuoteCharacter(), $this->platform->getStringLiteralQuoteCharacter());
1149 1150
    }

1151
    protected function getQuotedCommentOnColumnSQLWithoutQuoteCharacter(): string
1152 1153 1154 1155
    {
        return "COMMENT ON COLUMN mytable.id IS 'This is a comment'";
    }

1156
    public function testGetCommentOnColumnSQLWithoutQuoteCharacter(): void
1157
    {
1158
        self::assertEquals(
1159
            $this->getQuotedCommentOnColumnSQLWithoutQuoteCharacter(),
Sergei Morozov's avatar
Sergei Morozov committed
1160
            $this->platform->getCommentOnColumnSQL('mytable', 'id', 'This is a comment')
1161
        );
1162 1163
    }

1164
    protected function getQuotedCommentOnColumnSQLWithQuoteCharacter(): string
1165 1166 1167 1168
    {
        return "COMMENT ON COLUMN mytable.id IS 'It''s a quote !'";
    }

1169
    public function testGetCommentOnColumnSQLWithQuoteCharacter(): void
1170 1171
    {
        $c = $this->getStringLiteralQuoteCharacter();
1172

1173
        self::assertEquals(
1174
            $this->getQuotedCommentOnColumnSQLWithQuoteCharacter(),
Sergei Morozov's avatar
Sergei Morozov committed
1175
            $this->platform->getCommentOnColumnSQL('mytable', 'id', 'It' . $c . 's a quote !')
1176 1177 1178
        );
    }

1179 1180
    /**
     * @see testGetCommentOnColumnSQL
Sergei Morozov's avatar
Sergei Morozov committed
1181
     *
Sergei Morozov's avatar
Sergei Morozov committed
1182
     * @return string[]
1183
     */
1184
    abstract protected function getCommentOnColumnSQL(): array;
1185 1186 1187 1188

    /**
     * @group DBAL-1004
     */
1189
    public function testGetCommentOnColumnSQL(): void
1190
    {
1191
        self::assertSame(
1192
            $this->getCommentOnColumnSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1193
            [
Sergei Morozov's avatar
Sergei Morozov committed
1194 1195 1196
                $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
1197
            ]
1198 1199 1200
        );
    }

1201 1202 1203 1204
    /**
     * @group DBAL-1176
     * @dataProvider getGeneratesInlineColumnCommentSQL
     */
1205
    public function testGeneratesInlineColumnCommentSQL(?string $comment, string $expectedSql): void
1206
    {
Sergei Morozov's avatar
Sergei Morozov committed
1207
        if (! $this->platform->supportsInlineColumnComments()) {
1208
            self::markTestSkipped(sprintf('%s does not support inline column comments.', get_class($this->platform)));
1209 1210
        }

Sergei Morozov's avatar
Sergei Morozov committed
1211
        self::assertSame($expectedSql, $this->platform->getInlineColumnCommentSQL($comment));
1212 1213
    }

1214 1215 1216
    /**
     * @return mixed[][]
     */
1217
    public static function getGeneratesInlineColumnCommentSQL(): iterable
1218
    {
Sergei Morozov's avatar
Sergei Morozov committed
1219
        return [
1220
            'regular comment' => ['Regular comment', static::getInlineColumnRegularCommentSQL()],
Sergei Morozov's avatar
Sergei Morozov committed
1221
            'comment requiring escaping' => [
1222 1223
                sprintf(
                    'Using inline comment delimiter %s works',
1224
                    static::getInlineColumnCommentDelimiter()
1225
                ),
1226
                static::getInlineColumnCommentRequiringEscapingSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1227
            ],
1228
            'empty comment' => ['', static::getInlineColumnEmptyCommentSQL()],
Sergei Morozov's avatar
Sergei Morozov committed
1229
        ];
1230 1231
    }

1232
    protected static function getInlineColumnCommentDelimiter(): string
1233 1234 1235 1236
    {
        return "'";
    }

1237
    protected static function getInlineColumnRegularCommentSQL(): string
1238 1239 1240 1241
    {
        return "COMMENT 'Regular comment'";
    }

1242
    protected static function getInlineColumnCommentRequiringEscapingSQL(): string
1243 1244 1245 1246
    {
        return "COMMENT 'Using inline comment delimiter '' works'";
    }

1247
    protected static function getInlineColumnEmptyCommentSQL(): string
1248 1249 1250 1251
    {
        return "COMMENT ''";
    }

1252
    protected function getQuotedStringLiteralWithoutQuoteCharacter(): string
1253 1254 1255 1256
    {
        return "'No quote'";
    }

1257
    protected function getQuotedStringLiteralWithQuoteCharacter(): string
1258 1259 1260 1261
    {
        return "'It''s a quote'";
    }

1262
    protected function getQuotedStringLiteralQuoteCharacter(): string
1263 1264 1265 1266
    {
        return "''''";
    }

1267 1268 1269
    /**
     * @group DBAL-1176
     */
1270
    public function testThrowsExceptionOnGeneratingInlineColumnCommentSQLIfUnsupported(): void
1271
    {
Sergei Morozov's avatar
Sergei Morozov committed
1272
        if ($this->platform->supportsInlineColumnComments()) {
1273
            self::markTestSkipped(sprintf('%s supports inline column comments.', get_class($this->platform)));
1274 1275
        }

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

Sergei Morozov's avatar
Sergei Morozov committed
1280
        $this->platform->getInlineColumnCommentSQL('unsupported');
1281 1282
    }

1283
    public function testQuoteStringLiteral(): void
1284
    {
1285 1286
        $c = $this->getStringLiteralQuoteCharacter();

1287
        self::assertEquals(
1288
            $this->getQuotedStringLiteralWithoutQuoteCharacter(),
Sergei Morozov's avatar
Sergei Morozov committed
1289
            $this->platform->quoteStringLiteral('No quote')
1290
        );
1291
        self::assertEquals(
1292
            $this->getQuotedStringLiteralWithQuoteCharacter(),
Sergei Morozov's avatar
Sergei Morozov committed
1293
            $this->platform->quoteStringLiteral('It' . $c . 's a quote')
1294
        );
1295
        self::assertEquals(
1296
            $this->getQuotedStringLiteralQuoteCharacter(),
Sergei Morozov's avatar
Sergei Morozov committed
1297
            $this->platform->quoteStringLiteral($c)
1298
        );
1299
    }
1300 1301 1302 1303

    /**
     * @group DBAL-423
     */
1304
    public function testReturnsGuidTypeDeclarationSQL(): void
1305
    {
1306 1307
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
1308
        $this->platform->getGuidTypeDeclarationSQL([]);
1309
    }
1310 1311 1312 1313

    /**
     * @group DBAL-1010
     */
1314
    public function testGeneratesAlterTableRenameColumnSQL(): void
1315 1316 1317 1318 1319
    {
        $table = new Table('foo');
        $table->addColumn(
            'bar',
            'integer',
Sergei Morozov's avatar
Sergei Morozov committed
1320
            ['notnull' => true, 'default' => 666, 'comment' => 'rename test']
1321 1322
        );

Sergei Morozov's avatar
Sergei Morozov committed
1323 1324
        $tableDiff                        = new TableDiff('foo');
        $tableDiff->fromTable             = $table;
1325 1326 1327
        $tableDiff->renamedColumns['bar'] = new Column(
            'baz',
            Type::getType('integer'),
Sergei Morozov's avatar
Sergei Morozov committed
1328
            ['notnull' => true, 'default' => 666, 'comment' => 'rename test']
1329 1330
        );

Sergei Morozov's avatar
Sergei Morozov committed
1331
        self::assertSame($this->getAlterTableRenameColumnSQL(), $this->platform->getAlterTableSQL($tableDiff));
1332 1333 1334
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1335
     * @return string[]
1336
     */
1337
    abstract public function getAlterTableRenameColumnSQL(): array;
1338 1339 1340 1341

    /**
     * @group DBAL-1016
     */
1342
    public function testQuotesTableIdentifiersInAlterTableSQL(): void
1343 1344 1345 1346 1347 1348 1349 1350
    {
        $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
1351 1352
        $table->addForeignKeyConstraint('fk_table', ['fk'], ['id'], [], 'fk1');
        $table->addForeignKeyConstraint('fk_table', ['fk2'], ['id'], [], 'fk2');
1353

Sergei Morozov's avatar
Sergei Morozov committed
1354 1355 1356 1357
        $tableDiff                        = new TableDiff('"foo"');
        $tableDiff->fromTable             = $table;
        $tableDiff->newName               = 'table';
        $tableDiff->addedColumns['bloo']  = new Column('bloo', Type::getType('integer'));
1358 1359
        $tableDiff->changedColumns['bar'] = new ColumnDiff(
            'bar',
Sergei Morozov's avatar
Sergei Morozov committed
1360 1361
            new Column('bar', Type::getType('integer'), ['notnull' => false]),
            ['notnull'],
1362 1363
            $table->getColumn('bar')
        );
Sergei Morozov's avatar
Sergei Morozov committed
1364
        $tableDiff->renamedColumns['id']  = new Column('war', Type::getType('integer'));
1365
        $tableDiff->removedColumns['baz'] = new Column('baz', Type::getType('integer'));
Sergei Morozov's avatar
Sergei Morozov committed
1366 1367 1368
        $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');
1369

1370
        self::assertSame(
1371
            $this->getQuotesTableIdentifiersInAlterTableSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1372
            $this->platform->getAlterTableSQL($tableDiff)
1373 1374 1375 1376
        );
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1377
     * @return string[]
1378
     */
1379
    abstract protected function getQuotesTableIdentifiersInAlterTableSQL(): array;
1380 1381 1382 1383

    /**
     * @group DBAL-1090
     */
1384
    public function testAlterStringToFixedString(): void
1385 1386
    {
        $table = new Table('mytable');
Sergei Morozov's avatar
Sergei Morozov committed
1387
        $table->addColumn('name', 'string', ['length' => 2]);
1388

Sergei Morozov's avatar
Sergei Morozov committed
1389
        $tableDiff            = new TableDiff('mytable');
1390 1391
        $tableDiff->fromTable = $table;

Sergei Morozov's avatar
Sergei Morozov committed
1392 1393 1394 1395 1396 1397
        $tableDiff->changedColumns['name'] = new ColumnDiff(
            'name',
            new Column(
                'name',
                Type::getType('string'),
                ['fixed' => true, 'length' => 2]
1398
            ),
Sergei Morozov's avatar
Sergei Morozov committed
1399
            ['fixed']
1400 1401
        );

Sergei Morozov's avatar
Sergei Morozov committed
1402
        $sql = $this->platform->getAlterTableSQL($tableDiff);
1403 1404 1405

        $expectedSql = $this->getAlterStringToFixedStringSQL();

1406
        self::assertEquals($expectedSql, $sql);
1407 1408 1409
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1410
     * @return string[]
1411
     */
1412
    abstract protected function getAlterStringToFixedStringSQL(): array;
1413 1414 1415 1416

    /**
     * @group DBAL-1062
     */
1417
    public function testGeneratesAlterTableRenameIndexUsedByForeignKeySQL(): void
1418 1419 1420
    {
        $foreignTable = new Table('foreign_table');
        $foreignTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
1421
        $foreignTable->setPrimaryKey(['id']);
1422 1423 1424 1425 1426

        $primaryTable = new Table('mytable');
        $primaryTable->addColumn('foo', 'integer');
        $primaryTable->addColumn('bar', 'integer');
        $primaryTable->addColumn('baz', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
1427 1428 1429 1430
        $primaryTable->addIndex(['foo'], 'idx_foo');
        $primaryTable->addIndex(['bar'], 'idx_bar');
        $primaryTable->addForeignKeyConstraint($foreignTable, ['foo'], ['id'], [], 'fk_foo');
        $primaryTable->addForeignKeyConstraint($foreignTable, ['bar'], ['id'], [], 'fk_bar');
1431

Sergei Morozov's avatar
Sergei Morozov committed
1432 1433 1434
        $tableDiff                            = new TableDiff('mytable');
        $tableDiff->fromTable                 = $primaryTable;
        $tableDiff->renamedIndexes['idx_foo'] = new Index('idx_foo_renamed', ['foo']);
1435

1436
        self::assertSame(
1437
            $this->getGeneratesAlterTableRenameIndexUsedByForeignKeySQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1438
            $this->platform->getAlterTableSQL($tableDiff)
1439 1440 1441 1442
        );
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1443
     * @return string[]
1444
     */
1445
    abstract protected function getGeneratesAlterTableRenameIndexUsedByForeignKeySQL(): array;
1446 1447

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1448 1449
     * @param mixed[] $column
     *
1450 1451 1452
     * @group DBAL-1082
     * @dataProvider getGeneratesDecimalTypeDeclarationSQL
     */
1453
    public function testGeneratesDecimalTypeDeclarationSQL(array $column, string $expectedSql): void
1454
    {
Sergei Morozov's avatar
Sergei Morozov committed
1455
        self::assertSame($expectedSql, $this->platform->getDecimalTypeDeclarationSQL($column));
1456 1457 1458
    }

    /**
1459
     * @return mixed[][]
1460
     */
1461
    public static function getGeneratesDecimalTypeDeclarationSQL(): iterable
1462
    {
Sergei Morozov's avatar
Sergei Morozov committed
1463 1464 1465 1466 1467 1468 1469 1470
        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)'],
        ];
1471 1472 1473
    }

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

    /**
1485
     * @return mixed[][]
1486
     */
1487
    public static function getGeneratesFloatDeclarationSQL(): iterable
1488
    {
Sergei Morozov's avatar
Sergei Morozov committed
1489 1490 1491 1492 1493 1494 1495 1496
        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'],
        ];
1497
    }
1498

1499
    public function testItEscapesStringsForLike(): void
1500 1501 1502
    {
        self::assertSame(
            '\_25\% off\_ your next purchase \\\\o/',
Sergei Morozov's avatar
Sergei Morozov committed
1503
            $this->platform->escapeStringForLike('_25% off_ your next purchase \o/', '\\')
1504 1505
        );
    }
1506

1507
    public function testZeroOffsetWithoutLimitIsIgnored(): void
1508 1509 1510 1511 1512
    {
        $query = 'SELECT * FROM user';

        self::assertSame(
            $query,
Sergei Morozov's avatar
Sergei Morozov committed
1513
            $this->platform->modifyLimitQuery($query, null, 0)
1514 1515
        );
    }
1516
}
1517 1518 1519

interface GetCreateTableSqlDispatchEventListener
{
1520
    public function onSchemaCreateTable(): void;
1521

1522
    public function onSchemaCreateTableColumn(): void;
1523 1524 1525 1526
}

interface GetAlterTableSqlDispatchEventListener
{
1527
    public function onSchemaAlterTable(): void;
1528

1529
    public function onSchemaAlterTableAddColumn(): void;
1530

1531
    public function onSchemaAlterTableRemoveColumn(): void;
1532

1533
    public function onSchemaAlterTableChangeColumn(): void;
1534

1535
    public function onSchemaAlterTableRenameColumn(): void;
1536 1537 1538 1539
}

interface GetDropTableSqlDispatchEventListener
{
1540
    public function onSchemaDropTable(): void;
1541
}