AbstractPlatformTestCase.php 51 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\Tests\DBAL\Platforms;

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;
Sergei Morozov's avatar
Sergei Morozov committed
18
use Doctrine\Tests\DbalTestCase;
19
use Doctrine\Tests\Types\CommentedType;
20 21 22 23
use function get_class;
use function implode;
use function sprintf;
use function str_repeat;
24

Sergei Morozov's avatar
Sergei Morozov committed
25
abstract class AbstractPlatformTestCase extends DbalTestCase
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 44 45
            $this->markTestSkipped('Not working this way on mssql.');
        }

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 59 60
            $this->markTestSkipped('Not working this way on mssql.');
        }

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
    {
Sergei Morozov's avatar
Sergei Morozov committed
120
        if (! Type::hasType('my_commented')) {
121 122 123 124
            Type::addType('my_commented', CommentedType::class);
        }

        $type = Type::getType('my_commented');
Sergei Morozov's avatar
Sergei Morozov committed
125
        $this->platform->registerDoctrineTypeMapping('foo', 'my_commented');
126

Sergei Morozov's avatar
Sergei Morozov committed
127
        self::assertTrue($this->platform->isCommentedDoctrineType($type));
128 129
    }

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

139 140 141 142
    /**
     * @return mixed[]
     */
    public function getIsCommentedDoctrineType() : iterable
143 144 145
    {
        $this->setUp();

Sergei Morozov's avatar
Sergei Morozov committed
146
        $data = [];
147 148 149 150

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

Sergei Morozov's avatar
Sergei Morozov committed
151
            $data[$typeName] = [
152
                $type,
Sergei Morozov's avatar
Sergei Morozov committed
153
                $type->requiresSQLCommentHint($this->platform),
Sergei Morozov's avatar
Sergei Morozov committed
154
            ];
155 156 157 158 159
        }

        return $data;
    }

160
    public function testCreateWithNoColumns() : void
161
    {
162
        $table = new Table('test');
163

Sergei Morozov's avatar
Sergei Morozov committed
164 165
        $this->expectException(DBALException::class);
        $sql = $this->platform->getCreateTableSQL($table);
166 167
    }

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

Sergei Morozov's avatar
Sergei Morozov committed
175
        $sql = $this->platform->getCreateTableSQL($table);
176
        self::assertEquals($this->getGenerateTableSql(), $sql[0]);
177 178
    }

179
    abstract public function getGenerateTableSql() : string;
180

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

Sergei Morozov's avatar
Sergei Morozov committed
188
        $sql = $this->platform->getCreateTableSQL($table);
189
        self::assertEquals($this->getGenerateTableWithMultiColumnUniqueIndexSql(), $sql);
190 191
    }

192 193 194 195
    /**
     * @return string[]
     */
    abstract public function getGenerateTableWithMultiColumnUniqueIndexSql() : array;
196

197
    public function testGeneratesIndexCreationSql() : void
198
    {
Sergei Morozov's avatar
Sergei Morozov committed
199
        $indexDef = new Index('my_idx', ['user_name', 'last_login']);
200

201
        self::assertEquals(
202
            $this->getGenerateIndexSql(),
Sergei Morozov's avatar
Sergei Morozov committed
203
            $this->platform->getCreateIndexSQL($indexDef, 'mytable')
204 205 206
        );
    }

207
    abstract public function getGenerateIndexSql() : string;
208

209
    public function testGeneratesUniqueIndexCreationSql() : void
210
    {
Sergei Morozov's avatar
Sergei Morozov committed
211
        $indexDef = new Index('index_name', ['test', 'test2'], true);
212

Sergei Morozov's avatar
Sergei Morozov committed
213
        $sql = $this->platform->getCreateIndexSQL($indexDef, 'test');
214
        self::assertEquals($this->getGenerateUniqueIndexSql(), $sql);
215 216
    }

217
    abstract public function getGenerateUniqueIndexSql() : string;
218

219
    public function testGeneratesPartialIndexesSqlOnlyWhenSupportingPartialIndexes() : void
220
    {
Sergei Morozov's avatar
Sergei Morozov committed
221 222 223
        $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]);
224 225 226

        $expected = ' WHERE ' . $where;

Sergei Morozov's avatar
Sergei Morozov committed
227
        $actuals = [];
228 229

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

Sergei Morozov's avatar
Sergei Morozov committed
233 234
        $actuals[] = $this->platform->getUniqueConstraintDeclarationSQL('name', $uniqueIndex);
        $actuals[] = $this->platform->getCreateIndexSQL($indexDef, 'table');
235 236

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

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

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

253
    abstract protected function getGenerateForeignKeySql() : string;
254

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

364
        self::assertEquals($expectedSql, $sql);
365
    }
366

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

373
    public function testGetCreateTableSqlDispatchEvent() : void
374
    {
375
        $listenerMock = $this->createMock(GetCreateTableSqlDispatchEventListener::class);
376 377
        $listenerMock
            ->expects($this->once())
378
            ->method('onSchemaCreateTable');
379 380 381 382 383
        $listenerMock
            ->expects($this->exactly(2))
            ->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
        $listenerMock = $this->createMock(GetDropTableSqlDispatchEventListener::class);
398 399 400 401 402
        $listenerMock
            ->expects($this->once())
            ->method('onSchemaDropTable');

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

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

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

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

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

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

441 442 443 444 445
        $table = new Table('mytable');
        $table->addColumn('removed', 'integer');
        $table->addColumn('changed', 'integer');
        $table->addColumn('renamed', 'integer');

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

Sergei Morozov's avatar
Sergei Morozov committed
461
        $this->platform->getAlterTableSQL($tableDiff);
462
    }
463

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

Sergei Morozov's avatar
Sergei Morozov committed
473
        self::assertEquals($this->getCreateTableColumnCommentsSQL(), $this->platform->getCreateTableSQL($table));
474 475 476 477 478
    }

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

Sergei Morozov's avatar
Sergei Morozov committed
501
        self::assertEquals($this->getAlterTableColumnCommentsSQL(), $this->platform->getAlterTableSQL($tableDiff));
502 503
    }

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

Sergei Morozov's avatar
Sergei Morozov committed
511
        self::assertEquals($this->getCreateTableColumnTypeCommentsSQL(), $this->platform->getCreateTableSQL($table));
512 513
    }

514 515 516 517
    /**
     * @return string[]
     */
    public function getCreateTableColumnCommentsSQL() : array
518 519 520 521
    {
        $this->markTestSkipped('Platform does not support Column comments.');
    }

522 523 524 525
    /**
     * @return string[]
     */
    public function getAlterTableColumnCommentsSQL() : array
526 527 528
    {
        $this->markTestSkipped('Platform does not support Column comments.');
    }
529

530 531 532 533
    /**
     * @return string[]
     */
    public function getCreateTableColumnTypeCommentsSQL() : array
534 535 536 537
    {
        $this->markTestSkipped('Platform does not support Column comments.');
    }

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

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

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

561
            self::assertSame(
Sergei Morozov's avatar
Sergei Morozov committed
562 563
                ' DEFAULT ' . $this->platform->getCurrentTimestampSQL(),
                $this->platform->getDefaultValueDeclarationSQL($field)
564 565 566 567
            );
        }
    }

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

576
            self::assertEquals(
577
                ' DEFAULT 1',
Sergei Morozov's avatar
Sergei Morozov committed
578
                $this->platform->getDefaultValueDeclarationSQL($field)
579 580 581 582
            );
        }
    }

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

            self::assertSame(
                ' DEFAULT ' . $currentDateSql,
Sergei Morozov's avatar
Sergei Morozov committed
597
                $this->platform->getDefaultValueDeclarationSQL($field)
598 599
            );
        }
600 601
    }

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

610
        self::assertTrue($keywordList->isKeyword('table'));
611
    }
612 613 614 615

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

Sergei Morozov's avatar
Sergei Morozov committed
622
        $sql = $this->platform->getCreateTableSQL($table);
623
        self::assertEquals($this->getQuotedColumnInPrimaryKeySQL(), $sql);
624 625
    }

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

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

636 637 638 639
    /**
     * @return string[]
     */
    abstract protected function getQuotedNameInIndexSQL() : array;
640

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

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

Sergei Morozov's avatar
Sergei Morozov committed
655
        $sql = $this->platform->getCreateTableSQL($table);
656
        self::assertEquals($this->getQuotedColumnInIndexSQL(), $sql);
657
    }
658

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

Sergei Morozov's avatar
Sergei Morozov committed
665
        $sql = $this->platform->getCreateTableSQL($table);
666
        self::assertEquals($this->getQuotedNameInIndexSQL(), $sql);
Markus Fasselt's avatar
Markus Fasselt committed
667 668
    }

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

679
        // Foreign table with reserved keyword as name (needs quotation).
680
        $foreignTable = new Table('foreign');
681 682 683
        $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).
684

Sergei Morozov's avatar
Sergei Morozov committed
685
        $table->addForeignKeyConstraint($foreignTable, ['create', 'foo', '`bar`'], ['create', 'bar', '`foo-bar`'], [], 'FK_WITH_RESERVED_KEYWORD');
686 687 688 689 690 691 692

        // 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
693
        $table->addForeignKeyConstraint($foreignTable, ['create', 'foo', '`bar`'], ['create', 'bar', '`foo-bar`'], [], 'FK_WITH_NON_RESERVED_KEYWORD');
694 695 696 697 698 699 700

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

Sergei Morozov's avatar
Sergei Morozov committed
703
        $sql = $this->platform->getCreateTableSQL($table, AbstractPlatform::CREATE_FOREIGNKEYS);
704
        self::assertEquals($this->getQuotedColumnInForeignKeySQL(), $sql);
705
    }
706

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

714
        self::assertSame(
715
            $this->getQuotesReservedKeywordInUniqueConstraintDeclarationSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
716
            $this->platform->getUniqueConstraintDeclarationSQL('select', $index)
717 718 719
        );
    }

720
    abstract protected function getQuotesReservedKeywordInUniqueConstraintDeclarationSQL() : string;
721

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

733
    abstract protected function getQuotesReservedKeywordInTruncateTableSQL() : string;
734

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

        if (! $this->supportsInlineIndexDeclaration()) {
Sergei Morozov's avatar
Sergei Morozov committed
743
            $this->expectException(DBALException::class);
744 745
        }

746
        self::assertSame(
747
            $this->getQuotesReservedKeywordInIndexDeclarationSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
748
            $this->platform->getIndexDeclarationSQL('select', $index)
749 750 751
        );
    }

752
    abstract protected function getQuotesReservedKeywordInIndexDeclarationSQL() : string;
753

754
    protected function supportsInlineIndexDeclaration() : bool
755 756 757 758
    {
        return true;
    }

759
    public function testSupportsCommentOnStatement() : void
760
    {
Sergei Morozov's avatar
Sergei Morozov committed
761
        self::assertSame($this->supportsCommentOnStatement(), $this->platform->supportsCommentOnStatement());
762 763
    }

764
    protected function supportsCommentOnStatement() : bool
765 766 767 768
    {
        return false;
    }

769
    public function testGetCreateSchemaSQL() : void
770
    {
771 772
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
773
        $this->platform->getCreateSchemaSQL('schema');
774 775
    }

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

792
        self::assertStringContainsString(
Sergei Morozov's avatar
Sergei Morozov committed
793 794
            $this->platform->quoteIdentifier('select'),
            implode(';', $this->platform->getAlterTableSQL($tableDiff))
795 796
        );
    }
Benjamin Eberlei's avatar
Benjamin Eberlei committed
797

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

    /**
     * @group DBAL-563
     */
809
    public function testReturnsIdentitySequenceName() : void
810
    {
811 812
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
813
        $this->platform->getIdentitySequenceName('mytable', 'mycolumn');
814
    }
Steve Müller's avatar
Steve Müller committed
815

816
    public function testReturnsBinaryDefaultLength() : void
Steve Müller's avatar
Steve Müller committed
817
    {
Sergei Morozov's avatar
Sergei Morozov committed
818
        self::assertSame($this->getBinaryDefaultLength(), $this->platform->getBinaryDefaultLength());
Steve Müller's avatar
Steve Müller committed
819 820
    }

821
    protected function getBinaryDefaultLength() : int
Steve Müller's avatar
Steve Müller committed
822 823 824 825
    {
        return 255;
    }

826
    public function testReturnsBinaryMaxLength() : void
Steve Müller's avatar
Steve Müller committed
827
    {
Sergei Morozov's avatar
Sergei Morozov committed
828
        self::assertSame($this->getBinaryMaxLength(), $this->platform->getBinaryMaxLength());
Steve Müller's avatar
Steve Müller committed
829 830
    }

831
    protected function getBinaryMaxLength() : int
Steve Müller's avatar
Steve Müller committed
832 833 834 835
    {
        return 4000;
    }

836
    public function testReturnsBinaryTypeDeclarationSQL() : void
Steve Müller's avatar
Steve Müller committed
837
    {
838 839
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
840
        $this->platform->getBinaryTypeDeclarationSQL([]);
Steve Müller's avatar
Steve Müller committed
841
    }
842

843
    public function testReturnsBinaryTypeLongerThanMaxDeclarationSQL() : void
844 845 846 847
    {
        $this->markTestSkipped('Not applicable to the platform');
    }

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

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

867
        self::assertSame(
Sergei Morozov's avatar
Sergei Morozov committed
868 869
            $this->platform->getClobTypeDeclarationSQL($column),
            $this->platform->getJsonTypeDeclarationSQL($column)
870 871
        );
    }
872 873 874 875

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

886
        self::assertSame(
887
            $this->getAlterTableRenameIndexSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
888
            $this->platform->getAlterTableSQL($tableDiff)
889 890 891 892
        );
    }

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

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

919
        self::assertSame(
920
            $this->getQuotedAlterTableRenameIndexSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
921
            $this->platform->getAlterTableSQL($tableDiff)
922 923 924 925
        );
    }

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

    /**
     * @group DBAL-835
     */
943
    public function testQuotesAlterTableRenameColumn() : void
944 945 946
    {
        $fromTable = new Table('mytable');

Sergei Morozov's avatar
Sergei Morozov committed
947 948 949
        $fromTable->addColumn('unquoted1', 'integer', ['comment' => 'Unquoted 1']);
        $fromTable->addColumn('unquoted2', 'integer', ['comment' => 'Unquoted 2']);
        $fromTable->addColumn('unquoted3', 'integer', ['comment' => 'Unquoted 3']);
950

Sergei Morozov's avatar
Sergei Morozov committed
951 952 953
        $fromTable->addColumn('create', 'integer', ['comment' => 'Reserved keyword 1']);
        $fromTable->addColumn('table', 'integer', ['comment' => 'Reserved keyword 2']);
        $fromTable->addColumn('select', 'integer', ['comment' => 'Reserved keyword 3']);
954

Sergei Morozov's avatar
Sergei Morozov committed
955 956 957
        $fromTable->addColumn('`quoted1`', 'integer', ['comment' => 'Quoted 1']);
        $fromTable->addColumn('`quoted2`', 'integer', ['comment' => 'Quoted 2']);
        $fromTable->addColumn('`quoted3`', 'integer', ['comment' => 'Quoted 3']);
958 959 960

        $toTable = new Table('mytable');

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

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

Sergei Morozov's avatar
Sergei Morozov committed
969 970 971
        $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
972 973 974

        $comparator = new Comparator();

975
        self::assertEquals(
976
            $this->getQuotedAlterTableRenameColumnSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
977
            $this->platform->getAlterTableSQL($comparator->diffTable($fromTable, $toTable))
978 979 980 981 982 983
        );
    }

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

990 991 992
    /**
     * @group DBAL-835
     */
993
    public function testQuotesAlterTableChangeColumnLength() : void
994 995 996
    {
        $fromTable = new Table('mytable');

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

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

        $toTable = new Table('mytable');

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

Sergei Morozov's avatar
Sergei Morozov committed
1011 1012 1013
        $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]);
1014 1015 1016

        $comparator = new Comparator();

1017
        self::assertEquals(
1018
            $this->getQuotedAlterTableChangeColumnLengthSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1019
            $this->platform->getAlterTableSQL($comparator->diffTable($fromTable, $toTable))
1020 1021 1022 1023 1024 1025
        );
    }

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

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

1045
        self::assertSame(
1046
            $this->getAlterTableRenameIndexInSchemaSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1047
            $this->platform->getAlterTableSQL($tableDiff)
1048 1049 1050 1051
        );
    }

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

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

1078
        self::assertSame(
1079
            $this->getQuotedAlterTableRenameIndexInSchemaSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1080
            $this->platform->getAlterTableSQL($tableDiff)
1081 1082 1083 1084
        );
    }

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

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

Sergei Morozov's avatar
Sergei Morozov committed
1110 1111
        $tableName      = 'table';
        $table          = new Table($tableName);
1112
        $foreignKeyName = 'select';
Sergei Morozov's avatar
Sergei Morozov committed
1113 1114
        $foreignKey     = new ForeignKeyConstraint([], 'foo', [], 'select');
        $expectedSql    = $this->getQuotesDropForeignKeySQL();
1115

Sergei Morozov's avatar
Sergei Morozov committed
1116 1117
        self::assertSame($expectedSql, $this->platform->getDropForeignKeySQL($foreignKeyName, $tableName));
        self::assertSame($expectedSql, $this->platform->getDropForeignKeySQL($foreignKey, $table));
1118 1119
    }

1120
    protected function getQuotesDropForeignKeySQL() : string
1121 1122 1123 1124 1125 1126 1127
    {
        return 'ALTER TABLE "table" DROP FOREIGN KEY "select"';
    }

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

Sergei Morozov's avatar
Sergei Morozov committed
1136 1137
        self::assertSame($expectedSql, $this->platform->getDropConstraintSQL($constraintName, $tableName));
        self::assertSame($expectedSql, $this->platform->getDropConstraintSQL($constraint, $table));
1138 1139
    }

1140
    protected function getQuotesDropConstraintSQL() : string
1141 1142 1143 1144
    {
        return 'ALTER TABLE "table" DROP CONSTRAINT "select"';
    }

1145
    protected function getStringLiteralQuoteCharacter() : string
1146 1147 1148 1149
    {
        return "'";
    }

1150
    public function testGetStringLiteralQuoteCharacter() : void
1151
    {
Sergei Morozov's avatar
Sergei Morozov committed
1152
        self::assertSame($this->getStringLiteralQuoteCharacter(), $this->platform->getStringLiteralQuoteCharacter());
1153 1154
    }

1155
    protected function getQuotedCommentOnColumnSQLWithoutQuoteCharacter() : string
1156 1157 1158 1159
    {
        return "COMMENT ON COLUMN mytable.id IS 'This is a comment'";
    }

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

1168
    protected function getQuotedCommentOnColumnSQLWithQuoteCharacter() : string
1169 1170 1171 1172
    {
        return "COMMENT ON COLUMN mytable.id IS 'It''s a quote !'";
    }

1173
    public function testGetCommentOnColumnSQLWithQuoteCharacter() : void
1174 1175
    {
        $c = $this->getStringLiteralQuoteCharacter();
1176

1177
        self::assertEquals(
1178
            $this->getQuotedCommentOnColumnSQLWithQuoteCharacter(),
Sergei Morozov's avatar
Sergei Morozov committed
1179
            $this->platform->getCommentOnColumnSQL('mytable', 'id', 'It' . $c . 's a quote !')
1180 1181 1182
        );
    }

1183 1184
    /**
     * @see testGetCommentOnColumnSQL
Sergei Morozov's avatar
Sergei Morozov committed
1185
     *
Sergei Morozov's avatar
Sergei Morozov committed
1186
     * @return string[]
1187
     */
1188
    abstract protected function getCommentOnColumnSQL() : array;
1189 1190 1191 1192

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

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

Sergei Morozov's avatar
Sergei Morozov committed
1215
        self::assertSame($expectedSql, $this->platform->getInlineColumnCommentSQL($comment));
1216 1217
    }

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

1236
    protected static function getInlineColumnCommentDelimiter() : string
1237 1238 1239 1240
    {
        return "'";
    }

1241
    protected static function getInlineColumnRegularCommentSQL() : string
1242 1243 1244 1245
    {
        return "COMMENT 'Regular comment'";
    }

1246
    protected static function getInlineColumnCommentRequiringEscapingSQL() : string
1247 1248 1249 1250
    {
        return "COMMENT 'Using inline comment delimiter '' works'";
    }

1251
    protected static function getInlineColumnEmptyCommentSQL() : string
1252 1253 1254 1255
    {
        return "COMMENT ''";
    }

1256
    protected function getQuotedStringLiteralWithoutQuoteCharacter() : string
1257 1258 1259 1260
    {
        return "'No quote'";
    }

1261
    protected function getQuotedStringLiteralWithQuoteCharacter() : string
1262 1263 1264 1265
    {
        return "'It''s a quote'";
    }

1266
    protected function getQuotedStringLiteralQuoteCharacter() : string
1267 1268 1269 1270
    {
        return "''''";
    }

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

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

Sergei Morozov's avatar
Sergei Morozov committed
1284
        $this->platform->getInlineColumnCommentSQL('unsupported');
1285 1286
    }

1287
    public function testQuoteStringLiteral() : void
1288
    {
1289 1290
        $c = $this->getStringLiteralQuoteCharacter();

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

    /**
     * @group DBAL-423
     */
1308
    public function testReturnsGuidTypeDeclarationSQL() : void
1309
    {
1310 1311
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
1312
        $this->platform->getGuidTypeDeclarationSQL([]);
1313
    }
1314 1315 1316 1317

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

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

Sergei Morozov's avatar
Sergei Morozov committed
1335
        self::assertSame($this->getAlterTableRenameColumnSQL(), $this->platform->getAlterTableSQL($tableDiff));
1336 1337 1338
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1339
     * @return string[]
1340
     */
1341
    abstract public function getAlterTableRenameColumnSQL() : array;
1342 1343 1344 1345

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

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

1374
        self::assertSame(
1375
            $this->getQuotesTableIdentifiersInAlterTableSQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1376
            $this->platform->getAlterTableSQL($tableDiff)
1377 1378 1379 1380
        );
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1381
     * @return string[]
1382
     */
1383
    abstract protected function getQuotesTableIdentifiersInAlterTableSQL() : array;
1384 1385 1386 1387

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

Sergei Morozov's avatar
Sergei Morozov committed
1393
        $tableDiff            = new TableDiff('mytable');
1394 1395
        $tableDiff->fromTable = $table;

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

Sergei Morozov's avatar
Sergei Morozov committed
1406
        $sql = $this->platform->getAlterTableSQL($tableDiff);
1407 1408 1409

        $expectedSql = $this->getAlterStringToFixedStringSQL();

1410
        self::assertEquals($expectedSql, $sql);
1411 1412 1413
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1414
     * @return string[]
1415
     */
1416
    abstract protected function getAlterStringToFixedStringSQL() : array;
1417 1418 1419 1420

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

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

Sergei Morozov's avatar
Sergei Morozov committed
1436 1437 1438
        $tableDiff                            = new TableDiff('mytable');
        $tableDiff->fromTable                 = $primaryTable;
        $tableDiff->renamedIndexes['idx_foo'] = new Index('idx_foo_renamed', ['foo']);
1439

1440
        self::assertSame(
1441
            $this->getGeneratesAlterTableRenameIndexUsedByForeignKeySQL(),
Sergei Morozov's avatar
Sergei Morozov committed
1442
            $this->platform->getAlterTableSQL($tableDiff)
1443 1444 1445 1446
        );
    }

    /**
Sergei Morozov's avatar
Sergei Morozov committed
1447
     * @return string[]
1448
     */
1449
    abstract protected function getGeneratesAlterTableRenameIndexUsedByForeignKeySQL() : array;
1450 1451

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

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

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

    /**
1489
     * @return mixed[][]
1490
     */
1491
    public static function getGeneratesFloatDeclarationSQL() : iterable
1492
    {
Sergei Morozov's avatar
Sergei Morozov committed
1493 1494 1495 1496 1497 1498 1499 1500
        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'],
        ];
1501
    }
1502 1503 1504 1505 1506

    public function testItEscapesStringsForLike() : void
    {
        self::assertSame(
            '\_25\% off\_ your next purchase \\\\o/',
Sergei Morozov's avatar
Sergei Morozov committed
1507
            $this->platform->escapeStringForLike('_25% off_ your next purchase \o/', '\\')
1508 1509
        );
    }
1510 1511 1512 1513 1514 1515 1516

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

        self::assertSame(
            $query,
Sergei Morozov's avatar
Sergei Morozov committed
1517
            $this->platform->modifyLimitQuery($query, null, 0)
1518 1519
        );
    }
1520
}
1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545

interface GetCreateTableSqlDispatchEventListener
{
    public function onSchemaCreateTable() : void;

    public function onSchemaCreateTableColumn() : void;
}

interface GetAlterTableSqlDispatchEventListener
{
    public function onSchemaAlterTable() : void;

    public function onSchemaAlterTableAddColumn() : void;

    public function onSchemaAlterTableRemoveColumn() : void;

    public function onSchemaAlterTableChangeColumn() : void;

    public function onSchemaAlterTableRenameColumn() : void;
}

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