Passed
Pull Request — master (#3872)
by
unknown
62:31
created

testAutoQuotedColumnInPrimaryKeyPropagation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 9
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Tests\DBAL\Platforms;
6
7
use Doctrine\Common\EventManager;
8
use Doctrine\DBAL\DBALException;
9
use Doctrine\DBAL\Events;
10
use Doctrine\DBAL\Platforms\AbstractPlatform;
11
use Doctrine\DBAL\Platforms\Keywords\KeywordList;
12
use Doctrine\DBAL\Schema\Column;
13
use Doctrine\DBAL\Schema\ColumnDiff;
14
use Doctrine\DBAL\Schema\Comparator;
15
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
16
use Doctrine\DBAL\Schema\Index;
17
use Doctrine\DBAL\Schema\Table;
18
use Doctrine\DBAL\Schema\TableDiff;
19
use Doctrine\DBAL\Schema\UniqueConstraint;
20
use Doctrine\DBAL\Types\Type;
21
use Doctrine\Tests\DbalTestCase;
22
use Doctrine\Tests\Types\CommentedType;
23
use function get_class;
24
use function implode;
25
use function sprintf;
26
use function str_repeat;
27
28
abstract class AbstractPlatformTestCase extends DbalTestCase
29
{
30
    /** @var AbstractPlatform */
31
    protected $platform;
32
33
    abstract public function createPlatform() : AbstractPlatform;
34
35
    protected function setUp() : void
36
    {
37
        $this->platform = $this->createPlatform();
38
    }
39
40
    /**
41
     * @group DDC-1360
42
     */
43
    public function testQuoteIdentifier() : void
44
    {
45
        if ($this->platform->getName() === 'mssql') {
46
            $this->markTestSkipped('Not working this way on mssql.');
47
        }
48
49
        $c = $this->platform->getIdentifierQuoteCharacter();
50
        self::assertEquals($c . 'test' . $c, $this->platform->quoteIdentifier('test'));
51
        self::assertEquals($c . 'test' . $c . '.' . $c . 'test' . $c, $this->platform->quoteIdentifier('test.test'));
52
        self::assertEquals(str_repeat($c, 4), $this->platform->quoteIdentifier($c));
53
    }
54
55
    /**
56
     * @group DDC-1360
57
     */
58
    public function testQuoteSingleIdentifier() : void
59
    {
60
        if ($this->platform->getName() === 'mssql') {
61
            $this->markTestSkipped('Not working this way on mssql.');
62
        }
63
64
        $c = $this->platform->getIdentifierQuoteCharacter();
65
        self::assertEquals($c . 'test' . $c, $this->platform->quoteSingleIdentifier('test'));
66
        self::assertEquals($c . 'test.test' . $c, $this->platform->quoteSingleIdentifier('test.test'));
67
        self::assertEquals(str_repeat($c, 4), $this->platform->quoteSingleIdentifier($c));
68
    }
69
70
    /**
71
     * @group DBAL-1029
72
     * @dataProvider getReturnsForeignKeyReferentialActionSQL
73
     */
74
    public function testReturnsForeignKeyReferentialActionSQL(string $action, string $expectedSQL) : void
75
    {
76
        self::assertSame($expectedSQL, $this->platform->getForeignKeyReferentialActionSQL($action));
77
    }
78
79
    /**
80
     * @return mixed[][]
81
     */
82
    public static function getReturnsForeignKeyReferentialActionSQL() : iterable
83
    {
84
        return [
85
            ['CASCADE', 'CASCADE'],
86
            ['SET NULL', 'SET NULL'],
87
            ['NO ACTION', 'NO ACTION'],
88
            ['RESTRICT', 'RESTRICT'],
89
            ['SET DEFAULT', 'SET DEFAULT'],
90
            ['CaScAdE', 'CASCADE'],
91
        ];
92
    }
93
94
    public function testGetInvalidForeignKeyReferentialActionSQL() : void
95
    {
96
        $this->expectException('InvalidArgumentException');
97
        $this->platform->getForeignKeyReferentialActionSQL('unknown');
98
    }
99
100
    public function testGetUnknownDoctrineMappingType() : void
101
    {
102
        $this->expectException(DBALException::class);
103
        $this->platform->getDoctrineTypeMapping('foobar');
104
    }
105
106
    public function testRegisterDoctrineMappingType() : void
107
    {
108
        $this->platform->registerDoctrineTypeMapping('foo', 'integer');
109
        self::assertEquals('integer', $this->platform->getDoctrineTypeMapping('foo'));
110
    }
111
112
    public function testRegisterUnknownDoctrineMappingType() : void
113
    {
114
        $this->expectException(DBALException::class);
115
        $this->platform->registerDoctrineTypeMapping('foo', 'bar');
116
    }
117
118
    /**
119
     * @group DBAL-2594
120
     */
121
    public function testRegistersCommentedDoctrineMappingTypeImplicitly() : void
122
    {
123
        if (! Type::hasType('my_commented')) {
124
            Type::addType('my_commented', CommentedType::class);
125
        }
126
127
        $type = Type::getType('my_commented');
128
        $this->platform->registerDoctrineTypeMapping('foo', 'my_commented');
129
130
        self::assertTrue($this->platform->isCommentedDoctrineType($type));
131
    }
132
133
    /**
134
     * @group DBAL-939
135
     * @dataProvider getIsCommentedDoctrineType
136
     */
137
    public function testIsCommentedDoctrineType(Type $type, bool $commented) : void
138
    {
139
        self::assertSame($commented, $this->platform->isCommentedDoctrineType($type));
140
    }
141
142
    /**
143
     * @return mixed[]
144
     */
145
    public function getIsCommentedDoctrineType() : iterable
146
    {
147
        $this->setUp();
148
149
        $data = [];
150
151
        foreach (Type::getTypesMap() as $typeName => $className) {
152
            $type = Type::getType($typeName);
153
154
            $data[$typeName] = [
155
                $type,
156
                $type->requiresSQLCommentHint($this->platform),
157
            ];
158
        }
159
160
        return $data;
161
    }
162
163
    public function testCreateWithNoColumns() : void
164
    {
165
        $table = new Table('test');
166
167
        $this->expectException(DBALException::class);
168
        $this->platform->getCreateTableSQL($table);
169
    }
170
171
    public function testGeneratesTableCreationSql() : void
172
    {
173
        $table = new Table('test');
174
        $table->addColumn('id', 'integer', ['notnull' => true, 'autoincrement' => true]);
175
        $table->addColumn('test', 'string', ['notnull' => false, 'length' => 255]);
176
        $table->setPrimaryKey(['id']);
177
178
        $sql = $this->platform->getCreateTableSQL($table);
179
        self::assertEquals($this->getGenerateTableSql(), $sql[0]);
180
    }
181
182
    abstract public function getGenerateTableSql() : string;
183
184
    public function testGenerateTableWithMultiColumnUniqueIndex() : void
185
    {
186
        $table = new Table('test');
187
        $table->addColumn('foo', 'string', ['notnull' => false, 'length' => 255]);
188
        $table->addColumn('bar', 'string', ['notnull' => false, 'length' => 255]);
189
        $table->addUniqueIndex(['foo', 'bar']);
190
191
        $sql = $this->platform->getCreateTableSQL($table);
192
        self::assertEquals($this->getGenerateTableWithMultiColumnUniqueIndexSql(), $sql);
193
    }
194
195
    /**
196
     * @return string[]
197
     */
198
    abstract public function getGenerateTableWithMultiColumnUniqueIndexSql() : array;
199
200
    public function testGeneratesIndexCreationSql() : void
201
    {
202
        $indexDef = new Index('my_idx', ['user_name', 'last_login']);
203
204
        self::assertEquals(
205
            $this->getGenerateIndexSql(),
206
            $this->platform->getCreateIndexSQL($indexDef, 'mytable')
207
        );
208
    }
209
210
    abstract public function getGenerateIndexSql() : string;
211
212
    public function testGeneratesUniqueIndexCreationSql() : void
213
    {
214
        $indexDef = new Index('index_name', ['test', 'test2'], true);
215
216
        $sql = $this->platform->getCreateIndexSQL($indexDef, 'test');
217
        self::assertEquals($this->getGenerateUniqueIndexSql(), $sql);
218
    }
219
220
    abstract public function getGenerateUniqueIndexSql() : string;
221
222
    public function testGeneratesPartialIndexesSqlOnlyWhenSupportingPartialIndexes() : void
223
    {
224
        $where            = 'test IS NULL AND test2 IS NOT NULL';
225
        $indexDef         = new Index('name', ['test', 'test2'], false, false, [], ['where' => $where]);
226
        $uniqueConstraint = new UniqueConstraint('name', ['test', 'test2'], [], []);
227
228
        $expected = ' WHERE ' . $where;
229
230
        $actuals = [];
231
232
        if ($this->supportsInlineIndexDeclaration()) {
233
            $actuals[] = $this->platform->getIndexDeclarationSQL('name', $indexDef);
234
        }
235
236
        $uniqueConstraintSQL = $this->platform->getUniqueConstraintDeclarationSQL('name', $uniqueConstraint);
237
        $indexSQL            = $this->platform->getCreateIndexSQL($indexDef, 'table');
238
239
        $this->assertStringEndsNotWith($expected, $uniqueConstraintSQL, 'WHERE clause should NOT be present');
240
        if ($this->platform->supportsPartialIndexes()) {
241
            self::assertStringEndsWith($expected, $indexSQL, 'WHERE clause should be present');
242
        } else {
243
            self::assertStringEndsNotWith($expected, $indexSQL, 'WHERE clause should NOT be present');
244
        }
245
    }
246
247
    public function testGeneratesForeignKeyCreationSql() : void
248
    {
249
        $fk = new ForeignKeyConstraint(['fk_name_id'], 'other_table', ['id']);
250
251
        $sql = $this->platform->getCreateForeignKeySQL($fk, 'test');
252
        self::assertEquals($this->getGenerateForeignKeySql(), $sql);
253
    }
254
255
    abstract public function getGenerateForeignKeySql() : string;
256
257
    public function testGeneratesConstraintCreationSql() : void
258
    {
259
        $idx = new Index('constraint_name', ['test'], true, false);
260
        $sql = $this->platform->getCreateConstraintSQL($idx, 'test');
261
        self::assertEquals($this->getGenerateConstraintUniqueIndexSql(), $sql);
262
263
        $pk  = new Index('constraint_name', ['test'], true, true);
264
        $sql = $this->platform->getCreateConstraintSQL($pk, 'test');
265
        self::assertEquals($this->getGenerateConstraintPrimaryIndexSql(), $sql);
266
267
        $fk  = new ForeignKeyConstraint(['fk_name'], 'foreign', ['id'], 'constraint_fk');
268
        $sql = $this->platform->getCreateConstraintSQL($fk, 'test');
269
        self::assertEquals($this->getGenerateConstraintForeignKeySql($fk), $sql);
270
    }
271
272
    public function testGeneratesForeignKeySqlOnlyWhenSupportingForeignKeys() : void
273
    {
274
        $fk = new ForeignKeyConstraint(['fk_name'], 'foreign', ['id'], 'constraint_fk');
275
276
        if ($this->platform->supportsForeignKeyConstraints()) {
277
            self::assertIsString($this->platform->getCreateForeignKeySQL($fk, 'test'));
278
        } else {
279
            $this->expectException(DBALException::class);
280
            $this->platform->getCreateForeignKeySQL($fk, 'test');
281
        }
282
    }
283
284
    protected function getBitAndComparisonExpressionSql(string $value1, string $value2) : string
285
    {
286
        return '(' . $value1 . ' & ' . $value2 . ')';
287
    }
288
289
    /**
290
     * @group DDC-1213
291
     */
292
    public function testGeneratesBitAndComparisonExpressionSql() : void
293
    {
294
        $sql = $this->platform->getBitAndComparisonExpression('2', '4');
295
        self::assertEquals($this->getBitAndComparisonExpressionSql('2', '4'), $sql);
296
    }
297
298
    protected function getBitOrComparisonExpressionSql(string $value1, string $value2) : string
299
    {
300
        return '(' . $value1 . ' | ' . $value2 . ')';
301
    }
302
303
    /**
304
     * @group DDC-1213
305
     */
306
    public function testGeneratesBitOrComparisonExpressionSql() : void
307
    {
308
        $sql = $this->platform->getBitOrComparisonExpression('2', '4');
309
        self::assertEquals($this->getBitOrComparisonExpressionSql('2', '4'), $sql);
310
    }
311
312
    public function getGenerateConstraintUniqueIndexSql() : string
313
    {
314
        return 'ALTER TABLE test ADD CONSTRAINT constraint_name UNIQUE (test)';
315
    }
316
317
    public function getGenerateConstraintPrimaryIndexSql() : string
318
    {
319
        return 'ALTER TABLE test ADD CONSTRAINT constraint_name PRIMARY KEY (test)';
320
    }
321
322
    public function getGenerateConstraintForeignKeySql(ForeignKeyConstraint $fk) : string
323
    {
324
        $quotedForeignTable = $fk->getQuotedForeignTableName($this->platform);
325
326
        return sprintf(
327
            'ALTER TABLE test ADD CONSTRAINT constraint_fk FOREIGN KEY (fk_name) REFERENCES %s (id)',
328
            $quotedForeignTable
329
        );
330
    }
331
332
    /**
333
     * @return string[]
334
     */
335
    abstract public function getGenerateAlterTableSql() : array;
336
337
    public function testGeneratesTableAlterationSql() : void
338
    {
339
        $expectedSql = $this->getGenerateAlterTableSql();
340
341
        $table = new Table('mytable');
342
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
343
        $table->addColumn('foo', 'integer');
344
        $table->addColumn('bar', 'string');
345
        $table->addColumn('bloo', 'boolean');
346
        $table->setPrimaryKey(['id']);
347
348
        $tableDiff                        = new TableDiff('mytable');
349
        $tableDiff->fromTable             = $table;
350
        $tableDiff->newName               = 'userlist';
351
        $tableDiff->addedColumns['quota'] = new Column('quota', Type::getType('integer'), ['notnull' => false]);
352
        $tableDiff->removedColumns['foo'] = new Column('foo', Type::getType('integer'));
353
        $tableDiff->changedColumns['bar'] = new ColumnDiff(
354
            'bar',
355
            new Column(
356
                'baz',
357
                Type::getType('string'),
358
                [
359
                    'length' => 255,
360
                    'default' => 'def',
361
                ]
362
            ),
363
            ['type', 'notnull', 'default']
364
        );
365
366
        $tableDiff->changedColumns['bloo'] = new ColumnDiff(
367
            'bloo',
368
            new Column(
369
                'bloo',
370
                Type::getType('boolean'),
371
                ['default' => false]
372
            ),
373
            ['type', 'notnull', 'default']
374
        );
375
376
        $sql = $this->platform->getAlterTableSQL($tableDiff);
377
378
        self::assertEquals($expectedSql, $sql);
379
    }
380
381
    public function testGetCustomColumnDeclarationSql() : void
382
    {
383
        $field = ['columnDefinition' => 'MEDIUMINT(6) UNSIGNED'];
384
        self::assertEquals('foo MEDIUMINT(6) UNSIGNED', $this->platform->getColumnDeclarationSQL('foo', $field));
385
    }
386
387
    public function testGetCreateTableSqlDispatchEvent() : void
388
    {
389
        $listenerMock = $this->getMockBuilder($this->getMockClass('GetCreateTableSqlDispatchEvenListener'))
390
            ->addMethods(['onSchemaCreateTable', 'onSchemaCreateTableColumn'])
391
            ->getMock();
392
        $listenerMock
393
            ->expects($this->once())
394
            ->method('onSchemaCreateTable');
395
        $listenerMock
396
            ->expects($this->exactly(2))
397
            ->method('onSchemaCreateTableColumn');
398
399
        $eventManager = new EventManager();
400
        $eventManager->addEventListener([Events::onSchemaCreateTable, Events::onSchemaCreateTableColumn], $listenerMock);
401
402
        $this->platform->setEventManager($eventManager);
403
404
        $table = new Table('test');
405
        $table->addColumn('foo', 'string', ['notnull' => false, 'length' => 255]);
406
        $table->addColumn('bar', 'string', ['notnull' => false, 'length' => 255]);
407
408
        $this->platform->getCreateTableSQL($table);
409
    }
410
411
    public function testGetDropTableSqlDispatchEvent() : void
412
    {
413
        $listenerMock = $this->getMockBuilder($this->getMockClass('GetDropTableSqlDispatchEventListener'))
414
            ->addMethods(['onSchemaDropTable'])
415
            ->getMock();
416
        $listenerMock
417
            ->expects($this->once())
418
            ->method('onSchemaDropTable');
419
420
        $eventManager = new EventManager();
421
        $eventManager->addEventListener([Events::onSchemaDropTable], $listenerMock);
422
423
        $this->platform->setEventManager($eventManager);
424
425
        $this->platform->getDropTableSQL('TABLE');
426
    }
427
428
    public function testGetAlterTableSqlDispatchEvent() : void
429
    {
430
        $events = [
431
            'onSchemaAlterTable',
432
            'onSchemaAlterTableAddColumn',
433
            'onSchemaAlterTableRemoveColumn',
434
            'onSchemaAlterTableChangeColumn',
435
            'onSchemaAlterTableRenameColumn',
436
        ];
437
438
        $listenerMock = $this->getMockBuilder($this->getMockClass('GetAlterTableSqlDispatchEvenListener'))
439
            ->addMethods($events)
440
            ->getMock();
441
        $listenerMock
442
            ->expects($this->once())
443
            ->method('onSchemaAlterTable');
444
        $listenerMock
445
            ->expects($this->once())
446
            ->method('onSchemaAlterTableAddColumn');
447
        $listenerMock
448
            ->expects($this->once())
449
            ->method('onSchemaAlterTableRemoveColumn');
450
        $listenerMock
451
            ->expects($this->once())
452
            ->method('onSchemaAlterTableChangeColumn');
453
        $listenerMock
454
            ->expects($this->once())
455
            ->method('onSchemaAlterTableRenameColumn');
456
457
        $eventManager = new EventManager();
458
        $events       = [
459
            Events::onSchemaAlterTable,
460
            Events::onSchemaAlterTableAddColumn,
461
            Events::onSchemaAlterTableRemoveColumn,
462
            Events::onSchemaAlterTableChangeColumn,
463
            Events::onSchemaAlterTableRenameColumn,
464
        ];
465
        $eventManager->addEventListener($events, $listenerMock);
466
467
        $this->platform->setEventManager($eventManager);
468
469
        $table = new Table('mytable');
470
        $table->addColumn('removed', 'integer');
471
        $table->addColumn('changed', 'integer');
472
        $table->addColumn('renamed', 'integer');
473
474
        $tableDiff                            = new TableDiff('mytable');
475
        $tableDiff->fromTable                 = $table;
476
        $tableDiff->addedColumns['added']     = new Column('added', Type::getType('integer'), []);
477
        $tableDiff->removedColumns['removed'] = new Column('removed', Type::getType('integer'), []);
478
        $tableDiff->changedColumns['changed'] = new ColumnDiff(
479
            'changed',
480
            new Column('changed2', Type::getType('string'), ['length' => 255])
481
        );
482
        $tableDiff->renamedColumns['renamed'] = new Column('renamed2', Type::getType('integer'));
483
484
        $this->platform->getAlterTableSQL($tableDiff);
485
    }
486
487
    /**
488
     * @group DBAL-42
489
     */
490
    public function testCreateTableColumnComments() : void
491
    {
492
        $table = new Table('test');
493
        $table->addColumn('id', 'integer', ['comment' => 'This is a comment']);
494
        $table->setPrimaryKey(['id']);
495
496
        self::assertEquals($this->getCreateTableColumnCommentsSQL(), $this->platform->getCreateTableSQL($table));
497
    }
498
499
    /**
500
     * @group DBAL-42
501
     */
502
    public function testAlterTableColumnComments() : void
503
    {
504
        $tableDiff                        = new TableDiff('mytable');
505
        $tableDiff->addedColumns['quota'] = new Column('quota', Type::getType('integer'), ['comment' => 'A comment']);
506
        $tableDiff->changedColumns['foo'] = new ColumnDiff(
507
            'foo',
508
            new Column('foo', Type::getType('string'), ['length' => 255]),
509
            ['comment']
510
        );
511
        $tableDiff->changedColumns['bar'] = new ColumnDiff(
512
            'bar',
513
            new Column('baz', Type::getType('string'), [
514
                'length'  => 255,
515
                'comment' => 'B comment',
516
            ]),
517
            ['comment']
518
        );
519
520
        self::assertEquals($this->getAlterTableColumnCommentsSQL(), $this->platform->getAlterTableSQL($tableDiff));
521
    }
522
523
    public function testCreateTableColumnTypeComments() : void
524
    {
525
        $table = new Table('test');
526
        $table->addColumn('id', 'integer');
527
        $table->addColumn('data', 'array');
528
        $table->setPrimaryKey(['id']);
529
530
        self::assertEquals($this->getCreateTableColumnTypeCommentsSQL(), $this->platform->getCreateTableSQL($table));
531
    }
532
533
    /**
534
     * @return string[]
535
     */
536
    public function getCreateTableColumnCommentsSQL() : array
537
    {
538
        $this->markTestSkipped('Platform does not support Column comments.');
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return array. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
539
    }
540
541
    /**
542
     * @return string[]
543
     */
544
    public function getAlterTableColumnCommentsSQL() : array
545
    {
546
        $this->markTestSkipped('Platform does not support Column comments.');
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return array. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
547
    }
548
549
    /**
550
     * @return string[]
551
     */
552
    public function getCreateTableColumnTypeCommentsSQL() : array
553
    {
554
        $this->markTestSkipped('Platform does not support Column comments.');
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return array. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
555
    }
556
557
    public function testGetDefaultValueDeclarationSQL() : void
558
    {
559
        // non-timestamp value will get single quotes
560
        $field = [
561
            'type' => Type::getType('string'),
562
            'default' => 'non_timestamp',
563
        ];
564
565
        self::assertEquals(" DEFAULT 'non_timestamp'", $this->platform->getDefaultValueDeclarationSQL($field));
566
    }
567
568
    /**
569
     * @group 2859
570
     */
571
    public function testGetDefaultValueDeclarationSQLDateTime() : void
572
    {
573
        // timestamps on datetime types should not be quoted
574
        foreach (['datetime', 'datetimetz', 'datetime_immutable', 'datetimetz_immutable'] as $type) {
575
            $field = [
576
                'type'    => Type::getType($type),
577
                'default' => $this->platform->getCurrentTimestampSQL(),
578
            ];
579
580
            self::assertSame(
581
                ' DEFAULT ' . $this->platform->getCurrentTimestampSQL(),
582
                $this->platform->getDefaultValueDeclarationSQL($field)
583
            );
584
        }
585
    }
586
587
    public function testGetDefaultValueDeclarationSQLForIntegerTypes() : void
588
    {
589
        foreach (['bigint', 'integer', 'smallint'] as $type) {
590
            $field = [
591
                'type'    => Type::getType($type),
592
                'default' => 1,
593
            ];
594
595
            self::assertEquals(
596
                ' DEFAULT 1',
597
                $this->platform->getDefaultValueDeclarationSQL($field)
598
            );
599
        }
600
    }
601
602
    /**
603
     * @group 2859
604
     */
605
    public function testGetDefaultValueDeclarationSQLForDateType() : void
606
    {
607
        $currentDateSql = $this->platform->getCurrentDateSQL();
608
        foreach (['date', 'date_immutable'] as $type) {
609
            $field = [
610
                'type'    => Type::getType($type),
611
                'default' => $currentDateSql,
612
            ];
613
614
            self::assertSame(
615
                ' DEFAULT ' . $currentDateSql,
616
                $this->platform->getDefaultValueDeclarationSQL($field)
617
            );
618
        }
619
    }
620
621
    /**
622
     * @group DBAL-45
623
     */
624
    public function testKeywordList() : void
625
    {
626
        $keywordList = $this->platform->getReservedKeywordsList();
627
        self::assertInstanceOf(KeywordList::class, $keywordList);
628
629
        self::assertTrue($keywordList->isKeyword('table'));
630
    }
631
632
    public function getAutoQuotedIdentifiers() {
633
        return [
634
            ['create'],
635
            ['select'],
636
            ['a name with spaces'],
637
            ['a name with/slash'],
638
            ['name-with-dash'],
639
        ];
640
    }
641
642
    /**
643
     * @group DBAL-374
644
     * @group DBAL-2979
645
     * @dataProvider getAutoQuotedIdentifiers
646
     */
647
    public function testAutoQuotedColumnInPrimaryKeyPropagation(string $identifier) : void
648
    {
649
        $table = new Table('`quoted`');
650
        $table->addColumn($identifier, 'string', ['length' => 255]);
651
        $table->setPrimaryKey([$identifier]);
652
653
        $expected = preg_replace('/create/', $identifier, $this->getQuotedColumnInPrimaryKeySQL());
654
        $sql = $this->platform->getCreateTableSQL($table);
655
        self::assertEquals($expected, $sql);
656
    }
657
658
    /**
659
     * @return string[]
660
     */
661
    abstract protected function getQuotedColumnInPrimaryKeySQL() : array;
662
663
    /**
664
     * @return string[]
665
     */
666
    abstract protected function getQuotedColumnInIndexSQL() : array;
667
668
    /**
669
     * @return string[]
670
     */
671
    abstract protected function getQuotedNameInIndexSQL() : array;
672
673
    /**
674
     * @return string[]
675
     */
676
    abstract protected function getQuotedColumnInForeignKeySQL() : array;
677
678
    /**
679
     * @group DBAL-374
680
     */
681
    public function testQuotedColumnInIndexPropagation() : void
682
    {
683
        $table = new Table('`quoted`');
684
        $table->addColumn('create', 'string', ['length' => 255]);
685
        $table->addIndex(['create']);
686
687
        $sql = $this->platform->getCreateTableSQL($table);
688
        self::assertEquals($this->getQuotedColumnInIndexSQL(), $sql);
689
    }
690
691
    public function testQuotedNameInIndexSQL() : void
692
    {
693
        $table = new Table('test');
694
        $table->addColumn('column1', 'string', ['length' => 255]);
695
        $table->addIndex(['column1'], '`key`');
696
697
        $sql = $this->platform->getCreateTableSQL($table);
698
        self::assertEquals($this->getQuotedNameInIndexSQL(), $sql);
699
    }
700
701
    /**
702
     * @group DBAL-374
703
     * @group DBAL-2979
704
     * @dataProvider getAutoQuotedIdentifiers
705
     */
706
    public function testQuotedColumnInForeignKeyPropagation($identifier) : void
707
    {
708
        $table = new Table('`quoted`');
709
        $table->addColumn($identifier, 'string', ['length' => 255]);
710
        $table->addColumn('foo', 'string', ['length' => 255]);
711
        $table->addColumn('`bar`', 'string', ['length' => 255]);
712
713
        // Foreign table with reserved keyword as name (needs quotation).
714
        $foreignTable = new Table('foreign');
715
        $foreignTable->addColumn($identifier, 'string');    // Foreign column with reserved keyword as name (needs quotation).
716
        $foreignTable->addColumn('bar', 'string');       // Foreign column with non-reserved keyword as name (does not need quotation).
717
        $foreignTable->addColumn('`foo-bar`', 'string'); // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite).
718
719
        $table->addForeignKeyConstraint($foreignTable, [$identifier, 'foo', '`bar`'], [$identifier, 'bar', '`foo-bar`'], [], 'FK_WITH_RESERVED_KEYWORD');
720
721
        // Foreign table with non-reserved keyword as name (does not need quotation).
722
        $foreignTable = new Table('foo');
723
        $foreignTable->addColumn($identifier, 'string');    // Foreign column with reserved keyword as name (needs quotation).
724
        $foreignTable->addColumn('bar', 'string');       // Foreign column with non-reserved keyword as name (does not need quotation).
725
        $foreignTable->addColumn('`foo-bar`', 'string'); // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite).
726
727
        $table->addForeignKeyConstraint($foreignTable, [$identifier, 'foo', '`bar`'], [$identifier, 'bar', '`foo-bar`'], [], 'FK_WITH_NON_RESERVED_KEYWORD');
728
729
        // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite).
730
        $foreignTable = new Table('`foo-bar`');
731
        $foreignTable->addColumn($identifier, 'string');    // Foreign column with reserved keyword as name (needs quotation).
732
        $foreignTable->addColumn('bar', 'string');       // Foreign column with non-reserved keyword as name (does not need quotation).
733
        $foreignTable->addColumn('`foo-bar`', 'string'); // Foreign table with special character in name (needs quotation on some platforms, e.g. Sqlite).
734
735
        $table->addForeignKeyConstraint($foreignTable, [$identifier, 'foo', '`bar`'], [$identifier, 'bar', '`foo-bar`'], [], 'FK_WITH_INTENDED_QUOTATION');
736
737
        $expected = preg_replace('/create/', $identifier, $this->getQuotedColumnInForeignKeySQL());
738
        $sql = $this->platform->getCreateTableSQL($table, AbstractPlatform::CREATE_FOREIGNKEYS);
739
        self::assertEquals($expected, $sql);
740
    }
741
742
    /**
743
     * @group DBAL-1051
744
     * @group DBAL-2979
745
     * @dataProvider getAutoQuotedIdentifiers
746
     */
747
    public function testQuotesReservedKeywordInUniqueConstraintDeclarationSQL($identifier) : void
748
    {
749
        $constraint = new UniqueConstraint($identifier, ['foo'], [], []);
750
751
        $expected = preg_replace('/select/', $identifier, $this->getQuotesReservedKeywordInUniqueConstraintDeclarationSQL());
752
        self::assertSame(
753
            $expected,
754
            $this->platform->getUniqueConstraintDeclarationSQL($identifier, $constraint)
755
        );
756
    }
757
758
    abstract protected function getQuotesReservedKeywordInUniqueConstraintDeclarationSQL() : string;
759
760
    /**
761
     * @group DBAL-2270
762
     * @group DBAL-2979
763
     * @dataProvider getAutoQuotedIdentifiers
764
     */
765
    public function testQuotesReservedKeywordInTruncateTableSQL($identifier) : void
766
    {
767
        $expected = preg_replace('/select/', $identifier, $this->getQuotesReservedKeywordInTruncateTableSQL());
768
        self::assertSame(
769
            $expected,
770
            $this->platform->getTruncateTableSQL($identifier)
771
        );
772
    }
773
774
    abstract protected function getQuotesReservedKeywordInTruncateTableSQL() : string;
775
776
    /**
777
     * @group DBAL-1051
778
     * @group DBAL-2979
779
     * @dataProvider getAutoQuotedIdentifiers
780
     */
781
    public function testQuotesReservedKeywordInIndexDeclarationSQL($identifier) : void
782
    {
783
        $index = new Index($identifier, ['foo']);
784
785
        if (! $this->supportsInlineIndexDeclaration()) {
786
            $this->expectException(DBALException::class);
787
        }
788
789
        $expected = preg_replace('/select/', $identifier, $this->getQuotesReservedKeywordInIndexDeclarationSQL());
790
        self::assertSame(
791
            $expected,
792
            $this->platform->getIndexDeclarationSQL($identifier, $index)
793
        );
794
    }
795
796
    abstract protected function getQuotesReservedKeywordInIndexDeclarationSQL() : string;
797
798
    protected function supportsInlineIndexDeclaration() : bool
799
    {
800
        return true;
801
    }
802
803
    public function testSupportsCommentOnStatement() : void
804
    {
805
        self::assertSame($this->supportsCommentOnStatement(), $this->platform->supportsCommentOnStatement());
806
    }
807
808
    protected function supportsCommentOnStatement() : bool
809
    {
810
        return false;
811
    }
812
813
    public function testGetCreateSchemaSQL() : void
814
    {
815
        $this->expectException(DBALException::class);
816
817
        $this->platform->getCreateSchemaSQL('schema');
818
    }
819
820
    /**
821
     * @group DBAL-585
822
     */
823
    public function testAlterTableChangeQuotedColumn() : void
824
    {
825
        $tableDiff                        = new TableDiff('mytable');
826
        $tableDiff->fromTable             = new Table('mytable');
827
        $tableDiff->changedColumns['foo'] = new ColumnDiff(
828
            'select',
829
            new Column(
830
                'select',
831
                Type::getType('string'),
832
                ['length' => 255]
833
            ),
834
            ['type']
835
        );
836
837
        self::assertStringContainsString(
838
            $this->platform->quoteIdentifier('select'),
839
            implode(';', $this->platform->getAlterTableSQL($tableDiff))
840
        );
841
    }
842
843
    /**
844
     * @group DBAL-563
845
     */
846
    public function testUsesSequenceEmulatedIdentityColumns() : void
847
    {
848
        self::assertFalse($this->platform->usesSequenceEmulatedIdentityColumns());
849
    }
850
851
    /**
852
     * @group DBAL-563
853
     */
854
    public function testReturnsIdentitySequenceName() : void
855
    {
856
        $this->expectException(DBALException::class);
857
858
        $this->platform->getIdentitySequenceName('mytable', 'mycolumn');
859
    }
860
861
    public function testGetFixedLengthStringTypeDeclarationSQLNoLength() : void
862
    {
863
        self::assertSame(
864
            $this->getExpectedFixedLengthStringTypeDeclarationSQLNoLength(),
865
            $this->platform->getStringTypeDeclarationSQL(['fixed' => true])
866
        );
867
    }
868
869
    protected function getExpectedFixedLengthStringTypeDeclarationSQLNoLength() : string
870
    {
871
        return 'CHAR';
872
    }
873
874
    public function testGetFixedLengthStringTypeDeclarationSQLWithLength() : void
875
    {
876
        self::assertSame(
877
            $this->getExpectedFixedLengthStringTypeDeclarationSQLWithLength(),
878
            $this->platform->getStringTypeDeclarationSQL([
879
                'fixed' => true,
880
                'length' => 16,
881
            ])
882
        );
883
    }
884
885
    protected function getExpectedFixedLengthStringTypeDeclarationSQLWithLength() : string
886
    {
887
        return 'CHAR(16)';
888
    }
889
890
    public function testGetVariableLengthStringTypeDeclarationSQLNoLength() : void
891
    {
892
        self::assertSame(
893
            $this->getExpectedVariableLengthStringTypeDeclarationSQLNoLength(),
894
            $this->platform->getStringTypeDeclarationSQL([])
895
        );
896
    }
897
898
    protected function getExpectedVariableLengthStringTypeDeclarationSQLNoLength() : string
899
    {
900
        return 'VARCHAR';
901
    }
902
903
    public function testGetVariableLengthStringTypeDeclarationSQLWithLength() : void
904
    {
905
        self::assertSame(
906
            $this->getExpectedVariableLengthStringTypeDeclarationSQLWithLength(),
907
            $this->platform->getStringTypeDeclarationSQL(['length' => 16])
908
        );
909
    }
910
911
    protected function getExpectedVariableLengthStringTypeDeclarationSQLWithLength() : string
912
    {
913
        return 'VARCHAR(16)';
914
    }
915
916
    public function testGetFixedLengthBinaryTypeDeclarationSQLNoLength() : void
917
    {
918
        self::assertSame(
919
            $this->getExpectedFixedLengthBinaryTypeDeclarationSQLNoLength(),
920
            $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true])
921
        );
922
    }
923
924
    public function getExpectedFixedLengthBinaryTypeDeclarationSQLNoLength() : string
925
    {
926
        return 'BINARY';
927
    }
928
929
    public function testGetFixedLengthBinaryTypeDeclarationSQLWithLength() : void
930
    {
931
        self::assertSame(
932
            $this->getExpectedFixedLengthBinaryTypeDeclarationSQLWithLength(),
933
            $this->platform->getBinaryTypeDeclarationSQL([
934
                'fixed' => true,
935
                'length' => 16,
936
            ])
937
        );
938
    }
939
940
    public function getExpectedFixedLengthBinaryTypeDeclarationSQLWithLength() : string
941
    {
942
        return 'BINARY(16)';
943
    }
944
945
    public function testGetVariableLengthBinaryTypeDeclarationSQLNoLength() : void
946
    {
947
        self::assertSame(
948
            $this->getExpectedVariableLengthBinaryTypeDeclarationSQLNoLength(),
949
            $this->platform->getBinaryTypeDeclarationSQL([])
950
        );
951
    }
952
953
    public function getExpectedVariableLengthBinaryTypeDeclarationSQLNoLength() : string
954
    {
955
        return 'VARBINARY';
956
    }
957
958
    public function testGetVariableLengthBinaryTypeDeclarationSQLWithLength() : void
959
    {
960
        self::assertSame(
961
            $this->getExpectedVariableLengthBinaryTypeDeclarationSQLWithLength(),
962
            $this->platform->getBinaryTypeDeclarationSQL(['length' => 16])
963
        );
964
    }
965
966
    public function getExpectedVariableLengthBinaryTypeDeclarationSQLWithLength() : string
967
    {
968
        return 'VARBINARY(16)';
969
    }
970
971
    /**
972
     * @group DBAL-553
973
     */
974
    public function hasNativeJsonType() : void
975
    {
976
        self::assertFalse($this->platform->hasNativeJsonType());
977
    }
978
979
    /**
980
     * @group DBAL-553
981
     */
982
    public function testReturnsJsonTypeDeclarationSQL() : void
983
    {
984
        $column = [
985
            'length'  => 666,
986
            'notnull' => true,
987
            'type'    => Type::getType('json'),
988
        ];
989
990
        self::assertSame(
991
            $this->platform->getClobTypeDeclarationSQL($column),
992
            $this->platform->getJsonTypeDeclarationSQL($column)
993
        );
994
    }
995
996
    /**
997
     * @group DBAL-234
998
     */
999
    public function testAlterTableRenameIndex() : void
1000
    {
1001
        $tableDiff            = new TableDiff('mytable');
1002
        $tableDiff->fromTable = new Table('mytable');
1003
        $tableDiff->fromTable->addColumn('id', 'integer');
1004
        $tableDiff->fromTable->setPrimaryKey(['id']);
1005
        $tableDiff->renamedIndexes = [
1006
            'idx_foo' => new Index('idx_bar', ['id']),
1007
        ];
1008
1009
        self::assertSame(
1010
            $this->getAlterTableRenameIndexSQL(),
1011
            $this->platform->getAlterTableSQL($tableDiff)
1012
        );
1013
    }
1014
1015
    /**
1016
     * @return string[]
1017
     *
1018
     * @group DBAL-234
1019
     */
1020
    protected function getAlterTableRenameIndexSQL() : array
1021
    {
1022
        return [
1023
            'DROP INDEX idx_foo',
1024
            'CREATE INDEX idx_bar ON mytable (id)',
1025
        ];
1026
    }
1027
1028
    /**
1029
     * @group DBAL-234
1030
     */
1031
    public function testQuotesAlterTableRenameIndex() : void
1032
    {
1033
        $tableDiff            = new TableDiff('table');
1034
        $tableDiff->fromTable = new Table('table');
1035
        $tableDiff->fromTable->addColumn('id', 'integer');
1036
        $tableDiff->fromTable->setPrimaryKey(['id']);
1037
        $tableDiff->renamedIndexes = [
1038
            'create' => new Index('select', ['id']),
1039
            '`foo`'  => new Index('`bar`', ['id']),
1040
        ];
1041
1042
        self::assertSame(
1043
            $this->getQuotedAlterTableRenameIndexSQL(),
1044
            $this->platform->getAlterTableSQL($tableDiff)
1045
        );
1046
    }
1047
1048
    /**
1049
     * @return string[]
1050
     *
1051
     * @group DBAL-234
1052
     */
1053
    protected function getQuotedAlterTableRenameIndexSQL() : array
1054
    {
1055
        return [
1056
            'DROP INDEX "create"',
1057
            'CREATE INDEX "select" ON "table" (id)',
1058
            'DROP INDEX "foo"',
1059
            'CREATE INDEX "bar" ON "table" (id)',
1060
        ];
1061
    }
1062
1063
    /**
1064
     * @group DBAL-835
1065
     */
1066
    public function testQuotesAlterTableRenameColumn() : void
1067
    {
1068
        $fromTable = new Table('mytable');
1069
1070
        $fromTable->addColumn('unquoted1', 'integer', ['comment' => 'Unquoted 1']);
1071
        $fromTable->addColumn('unquoted2', 'integer', ['comment' => 'Unquoted 2']);
1072
        $fromTable->addColumn('unquoted3', 'integer', ['comment' => 'Unquoted 3']);
1073
1074
        $fromTable->addColumn('create', 'integer', ['comment' => 'Reserved keyword 1']);
1075
        $fromTable->addColumn('table', 'integer', ['comment' => 'Reserved keyword 2']);
1076
        $fromTable->addColumn('select', 'integer', ['comment' => 'Reserved keyword 3']);
1077
1078
        $fromTable->addColumn('`quoted1`', 'integer', ['comment' => 'Quoted 1']);
1079
        $fromTable->addColumn('`quoted2`', 'integer', ['comment' => 'Quoted 2']);
1080
        $fromTable->addColumn('`quoted3`', 'integer', ['comment' => 'Quoted 3']);
1081
1082
        $toTable = new Table('mytable');
1083
1084
        $toTable->addColumn('unquoted', 'integer', ['comment' => 'Unquoted 1']); // unquoted -> unquoted
1085
        $toTable->addColumn('where', 'integer', ['comment' => 'Unquoted 2']); // unquoted -> reserved keyword
1086
        $toTable->addColumn('`foo`', 'integer', ['comment' => 'Unquoted 3']); // unquoted -> quoted
1087
1088
        $toTable->addColumn('reserved_keyword', 'integer', ['comment' => 'Reserved keyword 1']); // reserved keyword -> unquoted
1089
        $toTable->addColumn('from', 'integer', ['comment' => 'Reserved keyword 2']); // reserved keyword -> reserved keyword
1090
        $toTable->addColumn('`bar`', 'integer', ['comment' => 'Reserved keyword 3']); // reserved keyword -> quoted
1091
1092
        $toTable->addColumn('quoted', 'integer', ['comment' => 'Quoted 1']); // quoted -> unquoted
1093
        $toTable->addColumn('and', 'integer', ['comment' => 'Quoted 2']); // quoted -> reserved keyword
1094
        $toTable->addColumn('`baz`', 'integer', ['comment' => 'Quoted 3']); // quoted -> quoted
1095
1096
        $comparator = new Comparator();
1097
1098
        $diff = $comparator->diffTable($fromTable, $toTable);
1099
1100
        self::assertNotNull($diff);
1101
1102
        self::assertEquals(
1103
            $this->getQuotedAlterTableRenameColumnSQL(),
1104
            $this->platform->getAlterTableSQL($diff)
1105
        );
1106
    }
1107
1108
    /**
1109
     * Returns SQL statements for {@link testQuotesAlterTableRenameColumn}.
1110
     *
1111
     * @return string[]
1112
     *
1113
     * @group DBAL-835
1114
     */
1115
    abstract protected function getQuotedAlterTableRenameColumnSQL() : array;
1116
1117
    /**
1118
     * @group DBAL-835
1119
     */
1120
    public function testQuotesAlterTableChangeColumnLength() : void
1121
    {
1122
        $fromTable = new Table('mytable');
1123
1124
        $fromTable->addColumn('unquoted1', 'string', ['comment' => 'Unquoted 1', 'length' => 10]);
1125
        $fromTable->addColumn('unquoted2', 'string', ['comment' => 'Unquoted 2', 'length' => 10]);
1126
        $fromTable->addColumn('unquoted3', 'string', ['comment' => 'Unquoted 3', 'length' => 10]);
1127
1128
        $fromTable->addColumn('create', 'string', ['comment' => 'Reserved keyword 1', 'length' => 10]);
1129
        $fromTable->addColumn('table', 'string', ['comment' => 'Reserved keyword 2', 'length' => 10]);
1130
        $fromTable->addColumn('select', 'string', ['comment' => 'Reserved keyword 3', 'length' => 10]);
1131
1132
        $toTable = new Table('mytable');
1133
1134
        $toTable->addColumn('unquoted1', 'string', ['comment' => 'Unquoted 1', 'length' => 255]);
1135
        $toTable->addColumn('unquoted2', 'string', ['comment' => 'Unquoted 2', 'length' => 255]);
1136
        $toTable->addColumn('unquoted3', 'string', ['comment' => 'Unquoted 3', 'length' => 255]);
1137
1138
        $toTable->addColumn('create', 'string', ['comment' => 'Reserved keyword 1', 'length' => 255]);
1139
        $toTable->addColumn('table', 'string', ['comment' => 'Reserved keyword 2', 'length' => 255]);
1140
        $toTable->addColumn('select', 'string', ['comment' => 'Reserved keyword 3', 'length' => 255]);
1141
1142
        $comparator = new Comparator();
1143
1144
        $diff = $comparator->diffTable($fromTable, $toTable);
1145
1146
        self::assertNotNull($diff);
1147
1148
        self::assertEquals(
1149
            $this->getQuotedAlterTableChangeColumnLengthSQL(),
1150
            $this->platform->getAlterTableSQL($diff)
1151
        );
1152
    }
1153
1154
    /**
1155
     * Returns SQL statements for {@link testQuotesAlterTableChangeColumnLength}.
1156
     *
1157
     * @return string[]
1158
     *
1159
     * @group DBAL-835
1160
     */
1161
    abstract protected function getQuotedAlterTableChangeColumnLengthSQL() : array;
1162
1163
    /**
1164
     * @group DBAL-807
1165
     */
1166
    public function testAlterTableRenameIndexInSchema() : void
1167
    {
1168
        $tableDiff            = new TableDiff('myschema.mytable');
1169
        $tableDiff->fromTable = new Table('myschema.mytable');
1170
        $tableDiff->fromTable->addColumn('id', 'integer');
1171
        $tableDiff->fromTable->setPrimaryKey(['id']);
1172
        $tableDiff->renamedIndexes = [
1173
            'idx_foo' => new Index('idx_bar', ['id']),
1174
        ];
1175
1176
        self::assertSame(
1177
            $this->getAlterTableRenameIndexInSchemaSQL(),
1178
            $this->platform->getAlterTableSQL($tableDiff)
1179
        );
1180
    }
1181
1182
    /**
1183
     * @return string[]
1184
     *
1185
     * @group DBAL-807
1186
     */
1187
    protected function getAlterTableRenameIndexInSchemaSQL() : array
1188
    {
1189
        return [
1190
            'DROP INDEX idx_foo',
1191
            'CREATE INDEX idx_bar ON myschema.mytable (id)',
1192
        ];
1193
    }
1194
1195
    /**
1196
     * @group DBAL-807
1197
     */
1198
    public function testQuotesAlterTableRenameIndexInSchema() : void
1199
    {
1200
        $tableDiff            = new TableDiff('`schema`.table');
1201
        $tableDiff->fromTable = new Table('`schema`.table');
1202
        $tableDiff->fromTable->addColumn('id', 'integer');
1203
        $tableDiff->fromTable->setPrimaryKey(['id']);
1204
        $tableDiff->renamedIndexes = [
1205
            'create' => new Index('select', ['id']),
1206
            '`foo`'  => new Index('`bar`', ['id']),
1207
        ];
1208
1209
        self::assertSame(
1210
            $this->getQuotedAlterTableRenameIndexInSchemaSQL(),
1211
            $this->platform->getAlterTableSQL($tableDiff)
1212
        );
1213
    }
1214
1215
    /**
1216
     * @return string[]
1217
     *
1218
     * @group DBAL-234
1219
     */
1220
    protected function getQuotedAlterTableRenameIndexInSchemaSQL() : array
1221
    {
1222
        return [
1223
            'DROP INDEX "schema"."create"',
1224
            'CREATE INDEX "select" ON "schema"."table" (id)',
1225
            'DROP INDEX "schema"."foo"',
1226
            'CREATE INDEX "bar" ON "schema"."table" (id)',
1227
        ];
1228
    }
1229
1230
    /**
1231
     * @group DBAL-1237
1232
     */
1233
    public function testQuotesDropForeignKeySQL() : void
1234
    {
1235
        if (! $this->platform->supportsForeignKeyConstraints()) {
1236
            $this->markTestSkipped(
1237
                sprintf('%s does not support foreign key constraints.', get_class($this->platform))
1238
            );
1239
        }
1240
1241
        $tableName      = 'table';
1242
        $table          = new Table($tableName);
1243
        $foreignKeyName = 'select';
1244
        $foreignKey     = new ForeignKeyConstraint([], 'foo', [], 'select');
1245
        $expectedSql    = $this->getQuotesDropForeignKeySQL();
1246
1247
        self::assertSame($expectedSql, $this->platform->getDropForeignKeySQL($foreignKeyName, $tableName));
1248
        self::assertSame($expectedSql, $this->platform->getDropForeignKeySQL($foreignKey, $table));
1249
    }
1250
1251
    protected function getQuotesDropForeignKeySQL() : string
1252
    {
1253
        return 'ALTER TABLE "table" DROP FOREIGN KEY "select"';
1254
    }
1255
1256
    /**
1257
     * @group DBAL-1237
1258
     */
1259
    public function testQuotesDropConstraintSQL() : void
1260
    {
1261
        $tableName      = 'table';
1262
        $table          = new Table($tableName);
1263
        $constraintName = 'select';
1264
        $constraint     = new ForeignKeyConstraint([], 'foo', [], 'select');
1265
        $expectedSql    = $this->getQuotesDropConstraintSQL();
1266
1267
        self::assertSame($expectedSql, $this->platform->getDropConstraintSQL($constraintName, $tableName));
1268
        self::assertSame($expectedSql, $this->platform->getDropConstraintSQL($constraint, $table));
1269
    }
1270
1271
    protected function getQuotesDropConstraintSQL() : string
1272
    {
1273
        return 'ALTER TABLE "table" DROP CONSTRAINT "select"';
1274
    }
1275
1276
    protected function getStringLiteralQuoteCharacter() : string
1277
    {
1278
        return "'";
1279
    }
1280
1281
    public function testGetStringLiteralQuoteCharacter() : void
1282
    {
1283
        self::assertSame($this->getStringLiteralQuoteCharacter(), $this->platform->getStringLiteralQuoteCharacter());
1284
    }
1285
1286
    protected function getQuotedCommentOnColumnSQLWithoutQuoteCharacter() : string
1287
    {
1288
        return "COMMENT ON COLUMN mytable.id IS 'This is a comment'";
1289
    }
1290
1291
    public function testGetCommentOnColumnSQLWithoutQuoteCharacter() : void
1292
    {
1293
        self::assertEquals(
1294
            $this->getQuotedCommentOnColumnSQLWithoutQuoteCharacter(),
1295
            $this->platform->getCommentOnColumnSQL('mytable', 'id', 'This is a comment')
1296
        );
1297
    }
1298
1299
    protected function getQuotedCommentOnColumnSQLWithQuoteCharacter() : string
1300
    {
1301
        return "COMMENT ON COLUMN mytable.id IS 'It''s a quote !'";
1302
    }
1303
1304
    public function testGetCommentOnColumnSQLWithQuoteCharacter() : void
1305
    {
1306
        $c = $this->getStringLiteralQuoteCharacter();
1307
1308
        self::assertEquals(
1309
            $this->getQuotedCommentOnColumnSQLWithQuoteCharacter(),
1310
            $this->platform->getCommentOnColumnSQL('mytable', 'id', 'It' . $c . 's a quote !')
1311
        );
1312
    }
1313
1314
    /**
1315
     * @see testGetCommentOnColumnSQL
1316
     *
1317
     * @return string[]
1318
     */
1319
    abstract protected function getCommentOnColumnSQL() : array;
1320
1321
    /**
1322
     * @group DBAL-1004
1323
     */
1324
    public function testGetCommentOnColumnSQL() : void
1325
    {
1326
        self::assertSame(
1327
            $this->getCommentOnColumnSQL(),
1328
            [
1329
                $this->platform->getCommentOnColumnSQL('foo', 'bar', 'comment'), // regular identifiers
1330
                $this->platform->getCommentOnColumnSQL('`Foo`', '`BAR`', 'comment'), // explicitly quoted identifiers
1331
                $this->platform->getCommentOnColumnSQL('select', 'from', 'comment'), // reserved keyword identifiers
1332
            ]
1333
        );
1334
    }
1335
1336
    /**
1337
     * @group DBAL-1176
1338
     * @dataProvider getGeneratesInlineColumnCommentSQL
1339
     */
1340
    public function testGeneratesInlineColumnCommentSQL(?string $comment, string $expectedSql) : void
1341
    {
1342
        if (! $this->platform->supportsInlineColumnComments()) {
1343
            $this->markTestSkipped(sprintf('%s does not support inline column comments.', get_class($this->platform)));
1344
        }
1345
1346
        self::assertSame($expectedSql, $this->platform->getInlineColumnCommentSQL($comment));
1347
    }
1348
1349
    /**
1350
     * @return mixed[][]
1351
     */
1352
    public static function getGeneratesInlineColumnCommentSQL() : iterable
1353
    {
1354
        return [
1355
            'regular comment' => ['Regular comment', static::getInlineColumnRegularCommentSQL()],
1356
            'comment requiring escaping' => [
1357
                sprintf(
1358
                    'Using inline comment delimiter %s works',
1359
                    static::getInlineColumnCommentDelimiter()
1360
                ),
1361
                static::getInlineColumnCommentRequiringEscapingSQL(),
1362
            ],
1363
            'empty comment' => ['', static::getInlineColumnEmptyCommentSQL()],
1364
        ];
1365
    }
1366
1367
    protected static function getInlineColumnCommentDelimiter() : string
1368
    {
1369
        return "'";
1370
    }
1371
1372
    protected static function getInlineColumnRegularCommentSQL() : string
1373
    {
1374
        return "COMMENT 'Regular comment'";
1375
    }
1376
1377
    protected static function getInlineColumnCommentRequiringEscapingSQL() : string
1378
    {
1379
        return "COMMENT 'Using inline comment delimiter '' works'";
1380
    }
1381
1382
    protected static function getInlineColumnEmptyCommentSQL() : string
1383
    {
1384
        return "COMMENT ''";
1385
    }
1386
1387
    protected function getQuotedStringLiteralWithoutQuoteCharacter() : string
1388
    {
1389
        return "'No quote'";
1390
    }
1391
1392
    protected function getQuotedStringLiteralWithQuoteCharacter() : string
1393
    {
1394
        return "'It''s a quote'";
1395
    }
1396
1397
    protected function getQuotedStringLiteralQuoteCharacter() : string
1398
    {
1399
        return "''''";
1400
    }
1401
1402
    /**
1403
     * @group DBAL-1176
1404
     */
1405
    public function testThrowsExceptionOnGeneratingInlineColumnCommentSQLIfUnsupported() : void
1406
    {
1407
        if ($this->platform->supportsInlineColumnComments()) {
1408
            $this->markTestSkipped(sprintf('%s supports inline column comments.', get_class($this->platform)));
1409
        }
1410
1411
        $this->expectException(DBALException::class);
1412
        $this->expectExceptionMessage('Operation "Doctrine\\DBAL\\Platforms\\AbstractPlatform::getInlineColumnCommentSQL" is not supported by platform.');
1413
        $this->expectExceptionCode(0);
1414
1415
        $this->platform->getInlineColumnCommentSQL('unsupported');
1416
    }
1417
1418
    public function testQuoteStringLiteral() : void
1419
    {
1420
        $c = $this->getStringLiteralQuoteCharacter();
1421
1422
        self::assertEquals(
1423
            $this->getQuotedStringLiteralWithoutQuoteCharacter(),
1424
            $this->platform->quoteStringLiteral('No quote')
1425
        );
1426
        self::assertEquals(
1427
            $this->getQuotedStringLiteralWithQuoteCharacter(),
1428
            $this->platform->quoteStringLiteral('It' . $c . 's a quote')
1429
        );
1430
        self::assertEquals(
1431
            $this->getQuotedStringLiteralQuoteCharacter(),
1432
            $this->platform->quoteStringLiteral($c)
1433
        );
1434
    }
1435
1436
    /**
1437
     * @group DBAL-423
1438
     */
1439
    public function testReturnsGuidTypeDeclarationSQL() : void
1440
    {
1441
        $this->expectException(DBALException::class);
1442
1443
        $this->platform->getGuidTypeDeclarationSQL([]);
1444
    }
1445
1446
    /**
1447
     * @group DBAL-1010
1448
     */
1449
    public function testGeneratesAlterTableRenameColumnSQL() : void
1450
    {
1451
        $table = new Table('foo');
1452
        $table->addColumn(
1453
            'bar',
1454
            'integer',
1455
            ['notnull' => true, 'default' => 666, 'comment' => 'rename test']
1456
        );
1457
1458
        $tableDiff                        = new TableDiff('foo');
1459
        $tableDiff->fromTable             = $table;
1460
        $tableDiff->renamedColumns['bar'] = new Column(
1461
            'baz',
1462
            Type::getType('integer'),
1463
            ['notnull' => true, 'default' => 666, 'comment' => 'rename test']
1464
        );
1465
1466
        self::assertSame($this->getAlterTableRenameColumnSQL(), $this->platform->getAlterTableSQL($tableDiff));
1467
    }
1468
1469
    /**
1470
     * @return string[]
1471
     */
1472
    abstract public function getAlterTableRenameColumnSQL() : array;
1473
1474
    /**
1475
     * @group DBAL-1016
1476
     */
1477
    public function testQuotesTableIdentifiersInAlterTableSQL() : void
1478
    {
1479
        $table = new Table('"foo"');
1480
        $table->addColumn('id', 'integer');
1481
        $table->addColumn('fk', 'integer');
1482
        $table->addColumn('fk2', 'integer');
1483
        $table->addColumn('fk3', 'integer');
1484
        $table->addColumn('bar', 'integer');
1485
        $table->addColumn('baz', 'integer');
1486
        $table->addForeignKeyConstraint('fk_table', ['fk'], ['id'], [], 'fk1');
1487
        $table->addForeignKeyConstraint('fk_table', ['fk2'], ['id'], [], 'fk2');
1488
1489
        $tableDiff                        = new TableDiff('"foo"');
1490
        $tableDiff->fromTable             = $table;
1491
        $tableDiff->newName               = 'table';
1492
        $tableDiff->addedColumns['bloo']  = new Column('bloo', Type::getType('integer'));
1493
        $tableDiff->changedColumns['bar'] = new ColumnDiff(
1494
            'bar',
1495
            new Column('bar', Type::getType('integer'), ['notnull' => false]),
1496
            ['notnull'],
1497
            $table->getColumn('bar')
1498
        );
1499
        $tableDiff->renamedColumns['id']  = new Column('war', Type::getType('integer'));
1500
        $tableDiff->removedColumns['baz'] = new Column('baz', Type::getType('integer'));
1501
        $tableDiff->addedForeignKeys[]    = new ForeignKeyConstraint(['fk3'], 'fk_table', ['id'], 'fk_add');
1502
        $tableDiff->changedForeignKeys[]  = new ForeignKeyConstraint(['fk2'], 'fk_table2', ['id'], 'fk2');
1503
        $tableDiff->removedForeignKeys[]  = new ForeignKeyConstraint(['fk'], 'fk_table', ['id'], 'fk1');
1504
1505
        self::assertSame(
1506
            $this->getQuotesTableIdentifiersInAlterTableSQL(),
1507
            $this->platform->getAlterTableSQL($tableDiff)
1508
        );
1509
    }
1510
1511
    /**
1512
     * @return string[]
1513
     */
1514
    abstract protected function getQuotesTableIdentifiersInAlterTableSQL() : array;
1515
1516
    /**
1517
     * @group DBAL-1090
1518
     */
1519
    public function testAlterStringToFixedString() : void
1520
    {
1521
        $table = new Table('mytable');
1522
        $table->addColumn('name', 'string', ['length' => 2]);
1523
1524
        $tableDiff            = new TableDiff('mytable');
1525
        $tableDiff->fromTable = $table;
1526
1527
        $tableDiff->changedColumns['name'] = new ColumnDiff(
1528
            'name',
1529
            new Column(
1530
                'name',
1531
                Type::getType('string'),
1532
                ['fixed' => true, 'length' => 2]
1533
            ),
1534
            ['fixed']
1535
        );
1536
1537
        $sql = $this->platform->getAlterTableSQL($tableDiff);
1538
1539
        $expectedSql = $this->getAlterStringToFixedStringSQL();
1540
1541
        self::assertEquals($expectedSql, $sql);
1542
    }
1543
1544
    /**
1545
     * @return string[]
1546
     */
1547
    abstract protected function getAlterStringToFixedStringSQL() : array;
1548
1549
    /**
1550
     * @group DBAL-1062
1551
     */
1552
    public function testGeneratesAlterTableRenameIndexUsedByForeignKeySQL() : void
1553
    {
1554
        $foreignTable = new Table('foreign_table');
1555
        $foreignTable->addColumn('id', 'integer');
1556
        $foreignTable->setPrimaryKey(['id']);
1557
1558
        $primaryTable = new Table('mytable');
1559
        $primaryTable->addColumn('foo', 'integer');
1560
        $primaryTable->addColumn('bar', 'integer');
1561
        $primaryTable->addColumn('baz', 'integer');
1562
        $primaryTable->addIndex(['foo'], 'idx_foo');
1563
        $primaryTable->addIndex(['bar'], 'idx_bar');
1564
        $primaryTable->addForeignKeyConstraint($foreignTable, ['foo'], ['id'], [], 'fk_foo');
1565
        $primaryTable->addForeignKeyConstraint($foreignTable, ['bar'], ['id'], [], 'fk_bar');
1566
1567
        $tableDiff                            = new TableDiff('mytable');
1568
        $tableDiff->fromTable                 = $primaryTable;
1569
        $tableDiff->renamedIndexes['idx_foo'] = new Index('idx_foo_renamed', ['foo']);
1570
1571
        self::assertSame(
1572
            $this->getGeneratesAlterTableRenameIndexUsedByForeignKeySQL(),
1573
            $this->platform->getAlterTableSQL($tableDiff)
1574
        );
1575
    }
1576
1577
    /**
1578
     * @return string[]
1579
     */
1580
    abstract protected function getGeneratesAlterTableRenameIndexUsedByForeignKeySQL() : array;
1581
1582
    /**
1583
     * @param mixed[] $column
1584
     *
1585
     * @group DBAL-1082
1586
     * @dataProvider getGeneratesDecimalTypeDeclarationSQL
1587
     */
1588
    public function testGeneratesDecimalTypeDeclarationSQL(array $column, string $expectedSql) : void
1589
    {
1590
        self::assertSame($expectedSql, $this->platform->getDecimalTypeDeclarationSQL($column));
1591
    }
1592
1593
    /**
1594
     * @return mixed[][]
1595
     */
1596
    public static function getGeneratesDecimalTypeDeclarationSQL() : iterable
1597
    {
1598
        return [
1599
            [[], 'NUMERIC(10, 0)'],
1600
            [['unsigned' => true], 'NUMERIC(10, 0)'],
1601
            [['unsigned' => false], 'NUMERIC(10, 0)'],
1602
            [['precision' => 5], 'NUMERIC(5, 0)'],
1603
            [['scale' => 5], 'NUMERIC(10, 5)'],
1604
            [['precision' => 8, 'scale' => 2], 'NUMERIC(8, 2)'],
1605
        ];
1606
    }
1607
1608
    /**
1609
     * @param mixed[] $column
1610
     *
1611
     * @group DBAL-1082
1612
     * @dataProvider getGeneratesFloatDeclarationSQL
1613
     */
1614
    public function testGeneratesFloatDeclarationSQL(array $column, string $expectedSql) : void
1615
    {
1616
        self::assertSame($expectedSql, $this->platform->getFloatDeclarationSQL($column));
1617
    }
1618
1619
    /**
1620
     * @return mixed[][]
1621
     */
1622
    public static function getGeneratesFloatDeclarationSQL() : iterable
1623
    {
1624
        return [
1625
            [[], 'DOUBLE PRECISION'],
1626
            [['unsigned' => true], 'DOUBLE PRECISION'],
1627
            [['unsigned' => false], 'DOUBLE PRECISION'],
1628
            [['precision' => 5], 'DOUBLE PRECISION'],
1629
            [['scale' => 5], 'DOUBLE PRECISION'],
1630
            [['precision' => 8, 'scale' => 2], 'DOUBLE PRECISION'],
1631
        ];
1632
    }
1633
1634
    public function testItEscapesStringsForLike() : void
1635
    {
1636
        self::assertSame(
1637
            '\_25\% off\_ your next purchase \\\\o/',
1638
            $this->platform->escapeStringForLike('_25% off_ your next purchase \o/', '\\')
1639
        );
1640
    }
1641
1642
    public function testZeroOffsetWithoutLimitIsIgnored() : void
1643
    {
1644
        $query = 'SELECT * FROM user';
1645
1646
        self::assertSame(
1647
            $query,
1648
            $this->platform->modifyLimitQuery($query, null, 0)
1649
        );
1650
    }
1651
}
1652