SchemaManagerFunctionalTestCase.php 64.9 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\Tests\DBAL\Functional\Schema;

5
use Doctrine\Common\EventManager;
6
use Doctrine\DBAL\DBALException;
Sergei Morozov's avatar
Sergei Morozov committed
7
use Doctrine\DBAL\Driver\Connection;
8
use Doctrine\DBAL\Events;
Thomas Müller's avatar
Thomas Müller committed
9
use Doctrine\DBAL\Platforms\OraclePlatform;
Sergei Morozov's avatar
Sergei Morozov committed
10 11
use Doctrine\DBAL\Schema\AbstractAsset;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
12 13
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ColumnDiff;
jeroendedauw's avatar
jeroendedauw committed
14
use Doctrine\DBAL\Schema\Comparator;
Sergei Morozov's avatar
Sergei Morozov committed
15 16
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Index;
17
use Doctrine\DBAL\Schema\SchemaDiff;
18
use Doctrine\DBAL\Schema\Sequence;
jeroendedauw's avatar
jeroendedauw committed
19
use Doctrine\DBAL\Schema\Table;
20
use Doctrine\DBAL\Schema\TableDiff;
Sergei Morozov's avatar
Sergei Morozov committed
21
use Doctrine\DBAL\Schema\View;
Sergei Morozov's avatar
Sergei Morozov committed
22 23 24 25 26 27 28 29 30
use Doctrine\DBAL\Types\ArrayType;
use Doctrine\DBAL\Types\BinaryType;
use Doctrine\DBAL\Types\DateIntervalType;
use Doctrine\DBAL\Types\DateTimeType;
use Doctrine\DBAL\Types\DecimalType;
use Doctrine\DBAL\Types\IntegerType;
use Doctrine\DBAL\Types\ObjectType;
use Doctrine\DBAL\Types\StringType;
use Doctrine\DBAL\Types\TextType;
jeroendedauw's avatar
jeroendedauw committed
31
use Doctrine\DBAL\Types\Type;
Sergei Morozov's avatar
Sergei Morozov committed
32
use Doctrine\Tests\DbalFunctionalTestCase;
33 34 35 36
use function array_filter;
use function array_keys;
use function array_map;
use function array_search;
37
use function array_values;
38 39 40 41 42
use function count;
use function current;
use function end;
use function explode;
use function in_array;
43
use function sprintf;
44
use function str_replace;
45
use function strcasecmp;
46 47 48
use function strlen;
use function strtolower;
use function substr;
49

50
abstract class SchemaManagerFunctionalTestCase extends DbalFunctionalTestCase
51
{
Sergei Morozov's avatar
Sergei Morozov committed
52
    /** @var AbstractSchemaManager */
Sergei Morozov's avatar
Sergei Morozov committed
53
    protected $schemaManager;
54

55
    protected function getPlatformName() : string
56
    {
Sergei Morozov's avatar
Sergei Morozov committed
57 58
        $class     = static::class;
        $e         = explode('\\', $class);
59
        $testClass = end($e);
60

Sergei Morozov's avatar
Sergei Morozov committed
61
        return strtolower(str_replace('SchemaManagerTest', null, $testClass));
62
    }
63

64
    protected function setUp() : void
65 66
    {
        parent::setUp();
67 68

        $dbms = $this->getPlatformName();
69

Sergei Morozov's avatar
Sergei Morozov committed
70
        if ($this->connection->getDatabasePlatform()->getName() !== $dbms) {
Sergei Morozov's avatar
Sergei Morozov committed
71
            $this->markTestSkipped(static::class . ' requires the use of ' . $dbms);
72 73
        }

Sergei Morozov's avatar
Sergei Morozov committed
74
        $this->schemaManager = $this->connection->getSchemaManager();
75 76
    }

77
    protected function tearDown() : void
78 79 80
    {
        parent::tearDown();

Sergei Morozov's avatar
Sergei Morozov committed
81
        $this->schemaManager->tryMethod('dropTable', 'testschema.my_table_in_namespace');
82 83 84 85

        //TODO: SchemaDiff does not drop removed namespaces?
        try {
            //sql server versions below 2016 do not support 'IF EXISTS' so we have to catch the exception here
Sergei Morozov's avatar
Sergei Morozov committed
86
            $this->connection->exec('DROP SCHEMA testschema');
87 88 89 90 91
        } catch (DBALException $e) {
            return;
        }
    }

92 93 94
    /**
     * @group DBAL-1220
     */
95
    public function testDropsDatabaseWithActiveConnections() : void
96
    {
Sergei Morozov's avatar
Sergei Morozov committed
97
        if (! $this->schemaManager->getDatabasePlatform()->supportsCreateDropDatabase()) {
98 99 100
            $this->markTestSkipped('Cannot drop Database client side with this Driver.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
101
        $this->schemaManager->dropAndCreateDatabase('test_drop_database');
102

Sergei Morozov's avatar
Sergei Morozov committed
103 104
        $knownDatabases = $this->schemaManager->listDatabases();
        if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) {
105
            self::assertContains('TEST_DROP_DATABASE', $knownDatabases);
Thomas Müller's avatar
Thomas Müller committed
106
        } else {
107
            self::assertContains('test_drop_database', $knownDatabases);
Thomas Müller's avatar
Thomas Müller committed
108
        }
109

Sergei Morozov's avatar
Sergei Morozov committed
110 111
        $params = $this->connection->getParams();
        if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) {
Thomas Müller's avatar
Thomas Müller committed
112 113 114 115
            $params['user'] = 'test_drop_database';
        } else {
            $params['dbname'] = 'test_drop_database';
        }
116

Sergei Morozov's avatar
Sergei Morozov committed
117
        $user     = $params['user'] ?? null;
118
        $password = $params['password'] ?? null;
119

Sergei Morozov's avatar
Sergei Morozov committed
120
        $connection = $this->connection->getDriver()->connect($params, $user, $password);
121

Sergei Morozov's avatar
Sergei Morozov committed
122
        self::assertInstanceOf(Connection::class, $connection);
123

Sergei Morozov's avatar
Sergei Morozov committed
124
        $this->schemaManager->dropDatabase('test_drop_database');
125

Sergei Morozov's avatar
Sergei Morozov committed
126
        self::assertNotContains('test_drop_database', $this->schemaManager->listDatabases());
127 128
    }

Benjamin Eberlei's avatar
Benjamin Eberlei committed
129 130 131
    /**
     * @group DBAL-195
     */
132
    public function testDropAndCreateSequence() : void
Benjamin Eberlei's avatar
Benjamin Eberlei committed
133
    {
134 135 136 137 138 139
        $platform = $this->connection->getDatabasePlatform();

        if (! $platform->supportsSequences()) {
            $this->markTestSkipped(
                sprintf('The "%s" platform does not support sequences.', $platform->getName())
            );
Benjamin Eberlei's avatar
Benjamin Eberlei committed
140 141
        }

Luís Cobucci's avatar
Luís Cobucci committed
142 143
        $name = 'dropcreate_sequences_test_seq';

Sergei Morozov's avatar
Sergei Morozov committed
144
        $this->schemaManager->dropAndCreateSequence(new Sequence($name, 20, 10));
Luís Cobucci's avatar
Luís Cobucci committed
145

Sergei Morozov's avatar
Sergei Morozov committed
146
        self::assertTrue($this->hasElementWithName($this->schemaManager->listSequences(), $name));
Luís Cobucci's avatar
Luís Cobucci committed
147 148
    }

Sergei Morozov's avatar
Sergei Morozov committed
149 150 151
    /**
     * @param AbstractAsset[] $items
     */
Luís Cobucci's avatar
Luís Cobucci committed
152 153 154 155
    private function hasElementWithName(array $items, string $name) : bool
    {
        $filteredList = array_filter(
            $items,
Sergei Morozov's avatar
Sergei Morozov committed
156
            static function (AbstractAsset $item) use ($name) : bool {
Luís Cobucci's avatar
Luís Cobucci committed
157 158 159 160 161
                return $item->getShortestName($item->getNamespaceName()) === $name;
            }
        );

        return count($filteredList) === 1;
Benjamin Eberlei's avatar
Benjamin Eberlei committed
162 163
    }

164
    public function testListSequences() : void
165
    {
166 167 168 169 170 171
        $platform = $this->connection->getDatabasePlatform();

        if (! $platform->supportsSequences()) {
            $this->markTestSkipped(
                sprintf('The "%s" platform does not support sequences.', $platform->getName())
            );
172 173
        }

174
        $sequence = new Sequence('list_sequences_test_seq', 20, 10);
Sergei Morozov's avatar
Sergei Morozov committed
175
        $this->schemaManager->createSequence($sequence);
176

Sergei Morozov's avatar
Sergei Morozov committed
177
        $sequences = $this->schemaManager->listSequences();
178

179
        self::assertIsArray($sequences, 'listSequences() should return an array.');
180 181

        $foundSequence = null;
182 183 184 185
        foreach ($sequences as $sequence) {
            self::assertInstanceOf(Sequence::class, $sequence, 'Array elements of listSequences() should be Sequence instances.');
            if (strtolower($sequence->getName()) !== 'list_sequences_test_seq') {
                continue;
186
            }
187 188

            $foundSequence = $sequence;
189 190
        }

191
        self::assertNotNull($foundSequence, "Sequence with name 'list_sequences_test_seq' was not found.");
192 193
        self::assertSame(20, $foundSequence->getAllocationSize(), 'Allocation Size is expected to be 20.');
        self::assertSame(10, $foundSequence->getInitialValue(), 'Initial Value is expected to be 10.');
194 195
    }

196
    public function testListDatabases() : void
197
    {
Sergei Morozov's avatar
Sergei Morozov committed
198
        if (! $this->schemaManager->getDatabasePlatform()->supportsCreateDropDatabase()) {
199 200 201
            $this->markTestSkipped('Cannot drop Database client side with this Driver.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
202 203
        $this->schemaManager->dropAndCreateDatabase('test_create_database');
        $databases = $this->schemaManager->listDatabases();
204

205
        $databases = array_map('strtolower', $databases);
206

207
        self::assertContains('test_create_database', $databases);
208 209
    }

210 211 212
    /**
     * @group DBAL-1058
     */
213
    public function testListNamespaceNames() : void
214
    {
Sergei Morozov's avatar
Sergei Morozov committed
215
        if (! $this->schemaManager->getDatabasePlatform()->supportsSchemas()) {
216 217 218 219
            $this->markTestSkipped('Platform does not support schemas.');
        }

        // Currently dropping schemas is not supported, so we have to workaround here.
Sergei Morozov's avatar
Sergei Morozov committed
220
        $namespaces = $this->schemaManager->listNamespaceNames();
221 222
        $namespaces = array_map('strtolower', $namespaces);

Sergei Morozov's avatar
Sergei Morozov committed
223
        if (! in_array('test_create_schema', $namespaces)) {
Sergei Morozov's avatar
Sergei Morozov committed
224
            $this->connection->executeUpdate($this->schemaManager->getDatabasePlatform()->getCreateSchemaSQL('test_create_schema'));
225

Sergei Morozov's avatar
Sergei Morozov committed
226
            $namespaces = $this->schemaManager->listNamespaceNames();
227 228 229
            $namespaces = array_map('strtolower', $namespaces);
        }

230
        self::assertContains('test_create_schema', $namespaces);
231 232
    }

233
    public function testListTables() : void
234 235
    {
        $this->createTestTable('list_tables_test');
Sergei Morozov's avatar
Sergei Morozov committed
236
        $tables = $this->schemaManager->listTables();
237

238
        self::assertIsArray($tables);
239
        self::assertTrue(count($tables) > 0, "List Tables has to find at least one table named 'list_tables_test'.");
240 241

        $foundTable = false;
jeroendedauw's avatar
jeroendedauw committed
242
        foreach ($tables as $table) {
Sergei Morozov's avatar
Sergei Morozov committed
243
            self::assertInstanceOf(Table::class, $table);
Sergei Morozov's avatar
Sergei Morozov committed
244 245
            if (strtolower($table->getName()) !== 'list_tables_test') {
                continue;
246
            }
Sergei Morozov's avatar
Sergei Morozov committed
247 248 249 250 251 252

            $foundTable = true;

            self::assertTrue($table->hasColumn('id'));
            self::assertTrue($table->hasColumn('test'));
            self::assertTrue($table->hasColumn('foreign_key_test'));
253
        }
254

Sergei Morozov's avatar
Sergei Morozov committed
255
        self::assertTrue($foundTable, "The 'list_tables_test' table has to be found.");
256 257
    }

258
    public function createListTableColumns() : Table
259
    {
260
        $table = new Table('list_table_columns');
Sergei Morozov's avatar
Sergei Morozov committed
261 262 263 264
        $table->addColumn('id', 'integer', ['notnull' => true]);
        $table->addColumn('test', 'string', ['length' => 255, 'notnull' => false, 'default' => 'expected default']);
        $table->addColumn('foo', 'text', ['notnull' => true]);
        $table->addColumn('bar', 'decimal', ['precision' => 10, 'scale' => 4, 'notnull' => false]);
265 266 267
        $table->addColumn('baz1', 'datetime');
        $table->addColumn('baz2', 'time');
        $table->addColumn('baz3', 'date');
Sergei Morozov's avatar
Sergei Morozov committed
268
        $table->setPrimaryKey(['id']);
269

270 271 272
        return $table;
    }

273
    public function testListTableColumns() : void
274 275 276
    {
        $table = $this->createListTableColumns();

Sergei Morozov's avatar
Sergei Morozov committed
277
        $this->schemaManager->dropAndCreateTable($table);
278

Sergei Morozov's avatar
Sergei Morozov committed
279
        $columns     = $this->schemaManager->listTableColumns('list_table_columns');
280
        $columnsKeys = array_keys($columns);
281

282 283
        self::assertArrayHasKey('id', $columns);
        self::assertEquals(0, array_search('id', $columnsKeys));
Sergei Morozov's avatar
Sergei Morozov committed
284
        self::assertEquals('id', strtolower($columns['id']->getname()));
Sergei Morozov's avatar
Sergei Morozov committed
285
        self::assertInstanceOf(IntegerType::class, $columns['id']->gettype());
Sergei Morozov's avatar
Sergei Morozov committed
286 287 288
        self::assertEquals(false, $columns['id']->getunsigned());
        self::assertEquals(true, $columns['id']->getnotnull());
        self::assertEquals(null, $columns['id']->getdefault());
289
        self::assertIsArray($columns['id']->getPlatformOptions());
290 291 292 293

        self::assertArrayHasKey('test', $columns);
        self::assertEquals(1, array_search('test', $columnsKeys));
        self::assertEquals('test', strtolower($columns['test']->getname()));
Sergei Morozov's avatar
Sergei Morozov committed
294
        self::assertInstanceOf(StringType::class, $columns['test']->gettype());
Sergei Morozov's avatar
Sergei Morozov committed
295 296 297 298
        self::assertEquals(255, $columns['test']->getlength());
        self::assertEquals(false, $columns['test']->getfixed());
        self::assertEquals(false, $columns['test']->getnotnull());
        self::assertEquals('expected default', $columns['test']->getdefault());
299
        self::assertIsArray($columns['test']->getPlatformOptions());
300

Sergei Morozov's avatar
Sergei Morozov committed
301
        self::assertEquals('foo', strtolower($columns['foo']->getname()));
302
        self::assertEquals(2, array_search('foo', $columnsKeys));
Sergei Morozov's avatar
Sergei Morozov committed
303
        self::assertInstanceOf(TextType::class, $columns['foo']->gettype());
Sergei Morozov's avatar
Sergei Morozov committed
304 305 306 307
        self::assertEquals(false, $columns['foo']->getunsigned());
        self::assertEquals(false, $columns['foo']->getfixed());
        self::assertEquals(true, $columns['foo']->getnotnull());
        self::assertEquals(null, $columns['foo']->getdefault());
308
        self::assertIsArray($columns['foo']->getPlatformOptions());
309

Sergei Morozov's avatar
Sergei Morozov committed
310
        self::assertEquals('bar', strtolower($columns['bar']->getname()));
311
        self::assertEquals(3, array_search('bar', $columnsKeys));
Sergei Morozov's avatar
Sergei Morozov committed
312
        self::assertInstanceOf(DecimalType::class, $columns['bar']->gettype());
Sergei Morozov's avatar
Sergei Morozov committed
313 314 315 316 317 318 319
        self::assertEquals(null, $columns['bar']->getlength());
        self::assertEquals(10, $columns['bar']->getprecision());
        self::assertEquals(4, $columns['bar']->getscale());
        self::assertEquals(false, $columns['bar']->getunsigned());
        self::assertEquals(false, $columns['bar']->getfixed());
        self::assertEquals(false, $columns['bar']->getnotnull());
        self::assertEquals(null, $columns['bar']->getdefault());
320
        self::assertIsArray($columns['bar']->getPlatformOptions());
321 322 323

        self::assertEquals('baz1', strtolower($columns['baz1']->getname()));
        self::assertEquals(4, array_search('baz1', $columnsKeys));
Sergei Morozov's avatar
Sergei Morozov committed
324
        self::assertInstanceOf(DateTimeType::class, $columns['baz1']->gettype());
Sergei Morozov's avatar
Sergei Morozov committed
325 326
        self::assertEquals(true, $columns['baz1']->getnotnull());
        self::assertEquals(null, $columns['baz1']->getdefault());
327
        self::assertIsArray($columns['baz1']->getPlatformOptions());
328 329 330

        self::assertEquals('baz2', strtolower($columns['baz2']->getname()));
        self::assertEquals(5, array_search('baz2', $columnsKeys));
Sergei Morozov's avatar
Sergei Morozov committed
331 332 333
        self::assertContains($columns['baz2']->gettype()->getName(), ['time', 'date', 'datetime']);
        self::assertEquals(true, $columns['baz2']->getnotnull());
        self::assertEquals(null, $columns['baz2']->getdefault());
334
        self::assertIsArray($columns['baz2']->getPlatformOptions());
335 336 337

        self::assertEquals('baz3', strtolower($columns['baz3']->getname()));
        self::assertEquals(6, array_search('baz3', $columnsKeys));
Sergei Morozov's avatar
Sergei Morozov committed
338 339 340
        self::assertContains($columns['baz3']->gettype()->getName(), ['time', 'date', 'datetime']);
        self::assertEquals(true, $columns['baz3']->getnotnull());
        self::assertEquals(null, $columns['baz3']->getdefault());
341
        self::assertIsArray($columns['baz3']->getPlatformOptions());
342 343
    }

344 345 346
    /**
     * @group DBAL-1078
     */
347
    public function testListTableColumnsWithFixedStringColumn() : void
348 349 350 351
    {
        $tableName = 'test_list_table_fixed_string';

        $table = new Table($tableName);
Sergei Morozov's avatar
Sergei Morozov committed
352
        $table->addColumn('column_char', 'string', ['fixed' => true, 'length' => 2]);
353

Sergei Morozov's avatar
Sergei Morozov committed
354
        $this->schemaManager->createTable($table);
355

Sergei Morozov's avatar
Sergei Morozov committed
356
        $columns = $this->schemaManager->listTableColumns($tableName);
357

358
        self::assertArrayHasKey('column_char', $columns);
Sergei Morozov's avatar
Sergei Morozov committed
359
        self::assertInstanceOf(StringType::class, $columns['column_char']->getType());
360 361
        self::assertTrue($columns['column_char']->getFixed());
        self::assertSame(2, $columns['column_char']->getLength());
362 363
    }

364
    public function testListTableColumnsDispatchEvent() : void
365 366 367
    {
        $table = $this->createListTableColumns();

Sergei Morozov's avatar
Sergei Morozov committed
368
        $this->schemaManager->dropAndCreateTable($table);
369

370 371
        $listenerMock = $this->getMockBuilder($this->getMockClass('ListTableColumnsDispatchEventListener'))
            ->addMethods(['onSchemaColumnDefinition'])
372
            ->getMock();
373

374 375 376 377
        $listenerMock
            ->expects($this->exactly(7))
            ->method('onSchemaColumnDefinition');

Sergei Morozov's avatar
Sergei Morozov committed
378
        $oldEventManager = $this->schemaManager->getDatabasePlatform()->getEventManager();
379 380

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

Sergei Morozov's avatar
Sergei Morozov committed
383
        $this->schemaManager->getDatabasePlatform()->setEventManager($eventManager);
384

Sergei Morozov's avatar
Sergei Morozov committed
385
        $this->schemaManager->listTableColumns('list_table_columns');
386

Sergei Morozov's avatar
Sergei Morozov committed
387
        $this->schemaManager->getDatabasePlatform()->setEventManager($oldEventManager);
388 389
    }

390
    public function testListTableIndexesDispatchEvent() : void
391 392
    {
        $table = $this->getTestTable('list_table_indexes_test');
Sergei Morozov's avatar
Sergei Morozov committed
393 394
        $table->addUniqueIndex(['test'], 'test_index_name');
        $table->addIndex(['id', 'test'], 'test_composite_idx');
395

Sergei Morozov's avatar
Sergei Morozov committed
396
        $this->schemaManager->dropAndCreateTable($table);
397

398 399
        $listenerMock = $this->getMockBuilder($this->getMockClass('ListTableIndexesDispatchEventListener'))
            ->addMethods(['onSchemaIndexDefinition'])
400
            ->getMock();
401 402 403 404
        $listenerMock
            ->expects($this->exactly(3))
            ->method('onSchemaIndexDefinition');

Sergei Morozov's avatar
Sergei Morozov committed
405
        $oldEventManager = $this->schemaManager->getDatabasePlatform()->getEventManager();
406 407

        $eventManager = new EventManager();
Sergei Morozov's avatar
Sergei Morozov committed
408
        $eventManager->addEventListener([Events::onSchemaIndexDefinition], $listenerMock);
409

Sergei Morozov's avatar
Sergei Morozov committed
410
        $this->schemaManager->getDatabasePlatform()->setEventManager($eventManager);
411

Sergei Morozov's avatar
Sergei Morozov committed
412
        $this->schemaManager->listTableIndexes('list_table_indexes_test');
413

Sergei Morozov's avatar
Sergei Morozov committed
414
        $this->schemaManager->getDatabasePlatform()->setEventManager($oldEventManager);
415 416
    }

417
    public function testDiffListTableColumns() : void
418
    {
Sergei Morozov's avatar
Sergei Morozov committed
419
        if ($this->schemaManager->getDatabasePlatform()->getName() === 'oracle') {
420 421 422 423
            $this->markTestSkipped('Does not work with Oracle, since it cannot detect DateTime, Date and Time differenecs (at the moment).');
        }

        $offlineTable = $this->createListTableColumns();
Sergei Morozov's avatar
Sergei Morozov committed
424 425
        $this->schemaManager->dropAndCreateTable($offlineTable);
        $onlineTable = $this->schemaManager->listTableDetails('list_table_columns');
426

Sergei Morozov's avatar
Sergei Morozov committed
427 428
        $comparator = new Comparator();
        $diff       = $comparator->diffTable($offlineTable, $onlineTable);
429

Sergei Morozov's avatar
Sergei Morozov committed
430
        self::assertFalse($diff, 'No differences should be detected with the offline vs online schema.');
431 432
    }

433
    public function testListTableIndexes() : void
434
    {
435
        $table = $this->getTestCompositeTable('list_table_indexes_test');
Sergei Morozov's avatar
Sergei Morozov committed
436 437
        $table->addUniqueIndex(['test'], 'test_index_name');
        $table->addIndex(['id', 'test'], 'test_composite_idx');
438

Sergei Morozov's avatar
Sergei Morozov committed
439
        $this->schemaManager->dropAndCreateTable($table);
440

Sergei Morozov's avatar
Sergei Morozov committed
441
        $tableIndexes = $this->schemaManager->listTableIndexes('list_table_indexes_test');
442

443
        self::assertEquals(3, count($tableIndexes));
444

445
        self::assertArrayHasKey('primary', $tableIndexes, 'listTableIndexes() has to return a "primary" array key.');
Sergei Morozov's avatar
Sergei Morozov committed
446
        self::assertEquals(['id', 'other_id'], array_map('strtolower', $tableIndexes['primary']->getColumns()));
447 448
        self::assertTrue($tableIndexes['primary']->isUnique());
        self::assertTrue($tableIndexes['primary']->isPrimary());
449

450
        self::assertEquals('test_index_name', strtolower($tableIndexes['test_index_name']->getName()));
Sergei Morozov's avatar
Sergei Morozov committed
451
        self::assertEquals(['test'], array_map('strtolower', $tableIndexes['test_index_name']->getColumns()));
452 453
        self::assertTrue($tableIndexes['test_index_name']->isUnique());
        self::assertFalse($tableIndexes['test_index_name']->isPrimary());
454

455
        self::assertEquals('test_composite_idx', strtolower($tableIndexes['test_composite_idx']->getName()));
Sergei Morozov's avatar
Sergei Morozov committed
456
        self::assertEquals(['id', 'test'], array_map('strtolower', $tableIndexes['test_composite_idx']->getColumns()));
457 458
        self::assertFalse($tableIndexes['test_composite_idx']->isUnique());
        self::assertFalse($tableIndexes['test_composite_idx']->isPrimary());
459 460
    }

461
    public function testDropAndCreateIndex() : void
462
    {
463
        $table = $this->getTestTable('test_create_index');
Sergei Morozov's avatar
Sergei Morozov committed
464
        $table->addUniqueIndex(['test'], 'test');
Sergei Morozov's avatar
Sergei Morozov committed
465
        $this->schemaManager->dropAndCreateTable($table);
466

Sergei Morozov's avatar
Sergei Morozov committed
467 468
        $this->schemaManager->dropAndCreateIndex($table->getIndex('test'), $table);
        $tableIndexes = $this->schemaManager->listTableIndexes('test_create_index');
469
        self::assertIsArray($tableIndexes);
470

Sergei Morozov's avatar
Sergei Morozov committed
471 472
        self::assertEquals('test', strtolower($tableIndexes['test']->getName()));
        self::assertEquals(['test'], array_map('strtolower', $tableIndexes['test']->getColumns()));
473 474
        self::assertTrue($tableIndexes['test']->isUnique());
        self::assertFalse($tableIndexes['test']->isPrimary());
475 476
    }

477
    public function testCreateTableWithForeignKeys() : void
478
    {
Sergei Morozov's avatar
Sergei Morozov committed
479
        if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) {
480 481 482 483 484
            $this->markTestSkipped('Platform does not support foreign keys.');
        }

        $tableB = $this->getTestTable('test_foreign');

Sergei Morozov's avatar
Sergei Morozov committed
485
        $this->schemaManager->dropAndCreateTable($tableB);
486 487

        $tableA = $this->getTestTable('test_create_fk');
Sergei Morozov's avatar
Sergei Morozov committed
488
        $tableA->addForeignKeyConstraint('test_foreign', ['foreign_key_test'], ['id']);
489

Sergei Morozov's avatar
Sergei Morozov committed
490
        $this->schemaManager->dropAndCreateTable($tableA);
491

Sergei Morozov's avatar
Sergei Morozov committed
492
        $fkTable       = $this->schemaManager->listTableDetails('test_create_fk');
493
        $fkConstraints = $fkTable->getForeignKeys();
494
        self::assertEquals(1, count($fkConstraints), "Table 'test_create_fk1' has to have one foreign key.");
495 496

        $fkConstraint = current($fkConstraints);
Sergei Morozov's avatar
Sergei Morozov committed
497
        self::assertInstanceOf(ForeignKeyConstraint::class, $fkConstraint);
Sergei Morozov's avatar
Sergei Morozov committed
498 499 500
        self::assertEquals('test_foreign', strtolower($fkConstraint->getForeignTableName()));
        self::assertEquals(['foreign_key_test'], array_map('strtolower', $fkConstraint->getColumns()));
        self::assertEquals(['id'], array_map('strtolower', $fkConstraint->getForeignColumns()));
501

Sergei Morozov's avatar
Sergei Morozov committed
502
        self::assertTrue($fkTable->columnsAreIndexed($fkConstraint->getColumns()), 'The columns of a foreign key constraint should always be indexed.');
503 504
    }

505
    public function testListForeignKeys() : void
506
    {
Sergei Morozov's avatar
Sergei Morozov committed
507
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
508 509 510 511 512 513
            $this->markTestSkipped('Does not support foreign key constraints.');
        }

        $this->createTestTable('test_create_fk1');
        $this->createTestTable('test_create_fk2');

Sergei Morozov's avatar
Sergei Morozov committed
514 515 516 517 518 519
        $foreignKey = new ForeignKeyConstraint(
            ['foreign_key_test'],
            'test_create_fk2',
            ['id'],
            'foreign_key_test_fk',
            ['onDelete' => 'CASCADE']
520 521
        );

Sergei Morozov's avatar
Sergei Morozov committed
522
        $this->schemaManager->createForeignKey($foreignKey, 'test_create_fk1');
523

Sergei Morozov's avatar
Sergei Morozov committed
524
        $fkeys = $this->schemaManager->listTableForeignKeys('test_create_fk1');
525

526
        self::assertEquals(1, count($fkeys), "Table 'test_create_fk1' has to have one foreign key.");
527

Sergei Morozov's avatar
Sergei Morozov committed
528
        self::assertInstanceOf(ForeignKeyConstraint::class, $fkeys[0]);
Sergei Morozov's avatar
Sergei Morozov committed
529 530 531
        self::assertEquals(['foreign_key_test'], array_map('strtolower', $fkeys[0]->getLocalColumns()));
        self::assertEquals(['id'], array_map('strtolower', $fkeys[0]->getForeignColumns()));
        self::assertEquals('test_create_fk2', strtolower($fkeys[0]->getForeignTableName()));
532

Sergei Morozov's avatar
Sergei Morozov committed
533 534
        if (! $fkeys[0]->hasOption('onDelete')) {
            return;
535
        }
Sergei Morozov's avatar
Sergei Morozov committed
536 537

        self::assertEquals('CASCADE', $fkeys[0]->getOption('onDelete'));
538 539
    }

540
    protected function getCreateExampleViewSql() : void
541 542 543 544
    {
        $this->markTestSkipped('No Create Example View SQL was defined for this SchemaManager');
    }

545
    public function testCreateSchema() : void
546 547 548
    {
        $this->createTestTable('test_table');

Sergei Morozov's avatar
Sergei Morozov committed
549
        $schema = $this->schemaManager->createSchema();
550
        self::assertTrue($schema->hasTable('test_table'));
551 552
    }

553
    public function testAlterTableScenario() : void
554
    {
Sergei Morozov's avatar
Sergei Morozov committed
555
        if (! $this->schemaManager->getDatabasePlatform()->supportsAlterTable()) {
556 557 558
            $this->markTestSkipped('Alter Table is not supported by this platform.');
        }

559
        $alterTable = $this->createTestTable('alter_table');
560 561
        $this->createTestTable('alter_table_foreign');

Sergei Morozov's avatar
Sergei Morozov committed
562
        $table = $this->schemaManager->listTableDetails('alter_table');
563 564 565 566 567
        self::assertTrue($table->hasColumn('id'));
        self::assertTrue($table->hasColumn('test'));
        self::assertTrue($table->hasColumn('foreign_key_test'));
        self::assertEquals(0, count($table->getForeignKeys()));
        self::assertEquals(1, count($table->getIndexes()));
568

Sergei Morozov's avatar
Sergei Morozov committed
569 570 571
        $tableDiff                         = new TableDiff('alter_table');
        $tableDiff->fromTable              = $alterTable;
        $tableDiff->addedColumns['foo']    = new Column('foo', Type::getType('integer'));
572 573
        $tableDiff->removedColumns['test'] = $table->getColumn('test');

Sergei Morozov's avatar
Sergei Morozov committed
574
        $this->schemaManager->alterTable($tableDiff);
575

Sergei Morozov's avatar
Sergei Morozov committed
576
        $table = $this->schemaManager->listTableDetails('alter_table');
577 578
        self::assertFalse($table->hasColumn('test'));
        self::assertTrue($table->hasColumn('foo'));
579

Sergei Morozov's avatar
Sergei Morozov committed
580 581 582
        $tableDiff                 = new TableDiff('alter_table');
        $tableDiff->fromTable      = $table;
        $tableDiff->addedIndexes[] = new Index('foo_idx', ['foo']);
583

Sergei Morozov's avatar
Sergei Morozov committed
584
        $this->schemaManager->alterTable($tableDiff);
585

Sergei Morozov's avatar
Sergei Morozov committed
586
        $table = $this->schemaManager->listTableDetails('alter_table');
587 588
        self::assertEquals(2, count($table->getIndexes()));
        self::assertTrue($table->hasIndex('foo_idx'));
Sergei Morozov's avatar
Sergei Morozov committed
589
        self::assertEquals(['foo'], array_map('strtolower', $table->getIndex('foo_idx')->getColumns()));
590 591
        self::assertFalse($table->getIndex('foo_idx')->isPrimary());
        self::assertFalse($table->getIndex('foo_idx')->isUnique());
592

Sergei Morozov's avatar
Sergei Morozov committed
593 594 595
        $tableDiff                   = new TableDiff('alter_table');
        $tableDiff->fromTable        = $table;
        $tableDiff->changedIndexes[] = new Index('foo_idx', ['foo', 'foreign_key_test']);
596

Sergei Morozov's avatar
Sergei Morozov committed
597
        $this->schemaManager->alterTable($tableDiff);
598

Sergei Morozov's avatar
Sergei Morozov committed
599
        $table = $this->schemaManager->listTableDetails('alter_table');
600 601
        self::assertEquals(2, count($table->getIndexes()));
        self::assertTrue($table->hasIndex('foo_idx'));
Sergei Morozov's avatar
Sergei Morozov committed
602
        self::assertEquals(['foo', 'foreign_key_test'], array_map('strtolower', $table->getIndex('foo_idx')->getColumns()));
603

Sergei Morozov's avatar
Sergei Morozov committed
604 605 606
        $tableDiff                            = new TableDiff('alter_table');
        $tableDiff->fromTable                 = $table;
        $tableDiff->renamedIndexes['foo_idx'] = new Index('bar_idx', ['foo', 'foreign_key_test']);
607

Sergei Morozov's avatar
Sergei Morozov committed
608
        $this->schemaManager->alterTable($tableDiff);
609

Sergei Morozov's avatar
Sergei Morozov committed
610
        $table = $this->schemaManager->listTableDetails('alter_table');
611 612 613
        self::assertEquals(2, count($table->getIndexes()));
        self::assertTrue($table->hasIndex('bar_idx'));
        self::assertFalse($table->hasIndex('foo_idx'));
Sergei Morozov's avatar
Sergei Morozov committed
614
        self::assertEquals(['foo', 'foreign_key_test'], array_map('strtolower', $table->getIndex('bar_idx')->getColumns()));
615 616
        self::assertFalse($table->getIndex('bar_idx')->isPrimary());
        self::assertFalse($table->getIndex('bar_idx')->isUnique());
617

Sergei Morozov's avatar
Sergei Morozov committed
618 619 620 621
        $tableDiff                     = new TableDiff('alter_table');
        $tableDiff->fromTable          = $table;
        $tableDiff->removedIndexes[]   = new Index('bar_idx', ['foo', 'foreign_key_test']);
        $fk                            = new ForeignKeyConstraint(['foreign_key_test'], 'alter_table_foreign', ['id']);
622 623
        $tableDiff->addedForeignKeys[] = $fk;

Sergei Morozov's avatar
Sergei Morozov committed
624 625
        $this->schemaManager->alterTable($tableDiff);
        $table = $this->schemaManager->listTableDetails('alter_table');
626 627

        // dont check for index size here, some platforms automatically add indexes for foreign keys.
628
        self::assertFalse($table->hasIndex('bar_idx'));
629

Sergei Morozov's avatar
Sergei Morozov committed
630
        if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) {
Sergei Morozov's avatar
Sergei Morozov committed
631
            return;
632
        }
Sergei Morozov's avatar
Sergei Morozov committed
633 634 635 636 637 638 639

        $fks = $table->getForeignKeys();
        self::assertCount(1, $fks);
        $foreignKey = current($fks);
        self::assertEquals('alter_table_foreign', strtolower($foreignKey->getForeignTableName()));
        self::assertEquals(['foreign_key_test'], array_map('strtolower', $foreignKey->getColumns()));
        self::assertEquals(['id'], array_map('strtolower', $foreignKey->getForeignColumns()));
640 641
    }

642
    public function testTableInNamespace() : void
643
    {
Sergei Morozov's avatar
Sergei Morozov committed
644
        if (! $this->schemaManager->getDatabasePlatform()->supportsSchemas()) {
645 646 647 648 649 650 651
            $this->markTestSkipped('Schema definition is not supported by this platform.');
        }

        //create schema
        $diff                  = new SchemaDiff();
        $diff->newNamespaces[] = 'testschema';

Sergei Morozov's avatar
Sergei Morozov committed
652 653
        foreach ($diff->toSql($this->schemaManager->getDatabasePlatform()) as $sql) {
            $this->connection->exec($sql);
654 655 656 657
        }

        //test if table is create in namespace
        $this->createTestTable('testschema.my_table_in_namespace');
Sergei Morozov's avatar
Sergei Morozov committed
658
        self::assertContains('testschema.my_table_in_namespace', $this->schemaManager->listTableNames());
659 660 661 662

        //tables without namespace should be created in default namespace
        //default namespaces are ignored in table listings
        $this->createTestTable('my_table_not_in_namespace');
Sergei Morozov's avatar
Sergei Morozov committed
663
        self::assertContains('my_table_not_in_namespace', $this->schemaManager->listTableNames());
664 665
    }

666
    public function testCreateAndListViews() : void
667
    {
Sergei Morozov's avatar
Sergei Morozov committed
668
        if (! $this->schemaManager->getDatabasePlatform()->supportsViews()) {
669 670 671
            $this->markTestSkipped('Views is not supported by this platform.');
        }

672
        $this->createTestTable('view_test_table');
673

Sergei Morozov's avatar
Sergei Morozov committed
674 675
        $name = 'doctrine_test_view';
        $sql  = 'SELECT * FROM view_test_table';
676

Sergei Morozov's avatar
Sergei Morozov committed
677
        $view = new View($name, $sql);
678

Sergei Morozov's avatar
Sergei Morozov committed
679
        $this->schemaManager->dropAndCreateView($view);
680

Sergei Morozov's avatar
Sergei Morozov committed
681
        self::assertTrue($this->hasElementWithName($this->schemaManager->listViews(), $name));
682 683
    }

684
    public function testAutoincrementDetection() : void
685
    {
Sergei Morozov's avatar
Sergei Morozov committed
686
        if (! $this->schemaManager->getDatabasePlatform()->supportsIdentityColumns()) {
687 688 689
            $this->markTestSkipped('This test is only supported on platforms that have autoincrement');
        }

690
        $table = new Table('test_autoincrement');
Sergei Morozov's avatar
Sergei Morozov committed
691
        $table->setSchemaConfig($this->schemaManager->createSchemaConfig());
Sergei Morozov's avatar
Sergei Morozov committed
692 693
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
        $table->setPrimaryKey(['id']);
694

Sergei Morozov's avatar
Sergei Morozov committed
695
        $this->schemaManager->createTable($table);
696

Sergei Morozov's avatar
Sergei Morozov committed
697
        $inferredTable = $this->schemaManager->listTableDetails('test_autoincrement');
698 699
        self::assertTrue($inferredTable->hasColumn('id'));
        self::assertTrue($inferredTable->getColumn('id')->getAutoincrement());
700
    }
701 702 703 704

    /**
     * @group DBAL-792
     */
705
    public function testAutoincrementDetectionMulticolumns() : void
706
    {
Sergei Morozov's avatar
Sergei Morozov committed
707
        if (! $this->schemaManager->getDatabasePlatform()->supportsIdentityColumns()) {
708 709
            $this->markTestSkipped('This test is only supported on platforms that have autoincrement');
        }
710

711
        $table = new Table('test_not_autoincrement');
Sergei Morozov's avatar
Sergei Morozov committed
712
        $table->setSchemaConfig($this->schemaManager->createSchemaConfig());
713 714
        $table->addColumn('id', 'integer');
        $table->addColumn('other_id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
715
        $table->setPrimaryKey(['id', 'other_id']);
716

Sergei Morozov's avatar
Sergei Morozov committed
717
        $this->schemaManager->createTable($table);
718

Sergei Morozov's avatar
Sergei Morozov committed
719
        $inferredTable = $this->schemaManager->listTableDetails('test_not_autoincrement');
720 721
        self::assertTrue($inferredTable->hasColumn('id'));
        self::assertFalse($inferredTable->getColumn('id')->getAutoincrement());
722
    }
723

724 725 726
    /**
     * @group DDC-887
     */
727
    public function testUpdateSchemaWithForeignKeyRenaming() : void
728
    {
Sergei Morozov's avatar
Sergei Morozov committed
729
        if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) {
730 731
            $this->markTestSkipped('This test is only supported on platforms that have foreign keys.');
        }
732

733
        $table = new Table('test_fk_base');
734
        $table->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
735
        $table->setPrimaryKey(['id']);
736

737
        $tableFK = new Table('test_fk_rename');
Sergei Morozov's avatar
Sergei Morozov committed
738
        $tableFK->setSchemaConfig($this->schemaManager->createSchemaConfig());
739 740
        $tableFK->addColumn('id', 'integer');
        $tableFK->addColumn('fk_id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
741 742 743
        $tableFK->setPrimaryKey(['id']);
        $tableFK->addIndex(['fk_id'], 'fk_idx');
        $tableFK->addForeignKeyConstraint('test_fk_base', ['fk_id'], ['id']);
744

Sergei Morozov's avatar
Sergei Morozov committed
745 746
        $this->schemaManager->createTable($table);
        $this->schemaManager->createTable($tableFK);
747

748
        $tableFKNew = new Table('test_fk_rename');
Sergei Morozov's avatar
Sergei Morozov committed
749
        $tableFKNew->setSchemaConfig($this->schemaManager->createSchemaConfig());
750 751
        $tableFKNew->addColumn('id', 'integer');
        $tableFKNew->addColumn('rename_fk_id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
752 753 754
        $tableFKNew->setPrimaryKey(['id']);
        $tableFKNew->addIndex(['rename_fk_id'], 'fk_idx');
        $tableFKNew->addForeignKeyConstraint('test_fk_base', ['rename_fk_id'], ['id']);
755

Sergei Morozov's avatar
Sergei Morozov committed
756
        $c         = new Comparator();
757
        $tableDiff = $c->diffTable($tableFK, $tableFKNew);
758

Sergei Morozov's avatar
Sergei Morozov committed
759
        $this->schemaManager->alterTable($tableDiff);
Luís Cobucci's avatar
Luís Cobucci committed
760

Sergei Morozov's avatar
Sergei Morozov committed
761
        $table       = $this->schemaManager->listTableDetails('test_fk_rename');
Luís Cobucci's avatar
Luís Cobucci committed
762 763 764 765 766
        $foreignKeys = $table->getForeignKeys();

        self::assertTrue($table->hasColumn('rename_fk_id'));
        self::assertCount(1, $foreignKeys);
        self::assertSame(['rename_fk_id'], array_map('strtolower', current($foreignKeys)->getColumns()));
767
    }
768

769 770 771
    /**
     * @group DBAL-1062
     */
772
    public function testRenameIndexUsedInForeignKeyConstraint() : void
773
    {
Sergei Morozov's avatar
Sergei Morozov committed
774
        if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) {
775 776 777 778 779
            $this->markTestSkipped('This test is only supported on platforms that have foreign keys.');
        }

        $primaryTable = new Table('test_rename_index_primary');
        $primaryTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
780
        $primaryTable->setPrimaryKey(['id']);
781 782 783

        $foreignTable = new Table('test_rename_index_foreign');
        $foreignTable->addColumn('fk', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
784
        $foreignTable->addIndex(['fk'], 'rename_index_fk_idx');
785 786
        $foreignTable->addForeignKeyConstraint(
            'test_rename_index_primary',
Sergei Morozov's avatar
Sergei Morozov committed
787 788 789
            ['fk'],
            ['id'],
            [],
790 791 792
            'fk_constraint'
        );

Sergei Morozov's avatar
Sergei Morozov committed
793 794
        $this->schemaManager->dropAndCreateTable($primaryTable);
        $this->schemaManager->dropAndCreateTable($foreignTable);
795 796 797 798 799 800

        $foreignTable2 = clone $foreignTable;
        $foreignTable2->renameIndex('rename_index_fk_idx', 'renamed_index_fk_idx');

        $comparator = new Comparator();

Sergei Morozov's avatar
Sergei Morozov committed
801
        $this->schemaManager->alterTable($comparator->diffTable($foreignTable, $foreignTable2));
802

Sergei Morozov's avatar
Sergei Morozov committed
803
        $foreignTable = $this->schemaManager->listTableDetails('test_rename_index_foreign');
804

805 806 807
        self::assertFalse($foreignTable->hasIndex('rename_index_fk_idx'));
        self::assertTrue($foreignTable->hasIndex('renamed_index_fk_idx'));
        self::assertTrue($foreignTable->hasForeignKey('fk_constraint'));
808 809
    }

810 811 812
    /**
     * @group DBAL-42
     */
813
    public function testGetColumnComment() : void
814
    {
Sergei Morozov's avatar
Sergei Morozov committed
815 816 817
        if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() &&
             ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() &&
            $this->connection->getDatabasePlatform()->getName() !== 'mssql') {
818 819
            $this->markTestSkipped('Database does not support column comments.');
        }
820

821
        $table = new Table('column_comment_test');
Sergei Morozov's avatar
Sergei Morozov committed
822 823
        $table->addColumn('id', 'integer', ['comment' => 'This is a comment']);
        $table->setPrimaryKey(['id']);
824

Sergei Morozov's avatar
Sergei Morozov committed
825
        $this->schemaManager->createTable($table);
826

Sergei Morozov's avatar
Sergei Morozov committed
827
        $columns = $this->schemaManager->listTableColumns('column_comment_test');
828 829
        self::assertEquals(1, count($columns));
        self::assertEquals('This is a comment', $columns['id']->getComment());
830

Sergei Morozov's avatar
Sergei Morozov committed
831 832 833 834 835 836 837
        $tableDiff                       = new TableDiff('column_comment_test');
        $tableDiff->fromTable            = $table;
        $tableDiff->changedColumns['id'] = new ColumnDiff(
            'id',
            new Column(
                'id',
                Type::getType('integer')
838
            ),
Sergei Morozov's avatar
Sergei Morozov committed
839 840 841 842 843
            ['comment'],
            new Column(
                'id',
                Type::getType('integer'),
                ['comment' => 'This is a comment']
844
            )
845 846
        );

Sergei Morozov's avatar
Sergei Morozov committed
847
        $this->schemaManager->alterTable($tableDiff);
848

Sergei Morozov's avatar
Sergei Morozov committed
849
        $columns = $this->schemaManager->listTableColumns('column_comment_test');
850 851
        self::assertEquals(1, count($columns));
        self::assertEmpty($columns['id']->getComment());
852 853
    }

854 855 856
    /**
     * @group DBAL-42
     */
857
    public function testAutomaticallyAppendCommentOnMarkedColumns() : void
858
    {
Sergei Morozov's avatar
Sergei Morozov committed
859 860 861
        if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() &&
             ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() &&
            $this->connection->getDatabasePlatform()->getName() !== 'mssql') {
862 863 864
            $this->markTestSkipped('Database does not support column comments.');
        }

865
        $table = new Table('column_comment_test2');
Sergei Morozov's avatar
Sergei Morozov committed
866 867 868 869
        $table->addColumn('id', 'integer', ['comment' => 'This is a comment']);
        $table->addColumn('obj', 'object', ['comment' => 'This is a comment']);
        $table->addColumn('arr', 'array', ['comment' => 'This is a comment']);
        $table->setPrimaryKey(['id']);
870

Sergei Morozov's avatar
Sergei Morozov committed
871
        $this->schemaManager->createTable($table);
872

Sergei Morozov's avatar
Sergei Morozov committed
873
        $columns = $this->schemaManager->listTableColumns('column_comment_test2');
874 875
        self::assertEquals(3, count($columns));
        self::assertEquals('This is a comment', $columns['id']->getComment());
Sergei Morozov's avatar
Sergei Morozov committed
876
        self::assertEquals('This is a comment', $columns['obj']->getComment(), 'The Doctrine2 Typehint should be stripped from comment.');
Sergei Morozov's avatar
Sergei Morozov committed
877
        self::assertInstanceOf(ObjectType::class, $columns['obj']->getType(), 'The Doctrine2 should be detected from comment hint.');
Sergei Morozov's avatar
Sergei Morozov committed
878
        self::assertEquals('This is a comment', $columns['arr']->getComment(), 'The Doctrine2 Typehint should be stripped from comment.');
Sergei Morozov's avatar
Sergei Morozov committed
879
        self::assertInstanceOf(ArrayType::class, $columns['arr']->getType(), 'The Doctrine2 should be detected from comment hint.');
880 881
    }

882 883 884
    /**
     * @group DBAL-1228
     */
885
    public function testCommentHintOnDateIntervalTypeColumn() : void
886
    {
Sergei Morozov's avatar
Sergei Morozov committed
887 888 889
        if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() &&
            ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() &&
            $this->connection->getDatabasePlatform()->getName() !== 'mssql') {
890 891 892 893
            $this->markTestSkipped('Database does not support column comments.');
        }

        $table = new Table('column_dateinterval_comment');
Sergei Morozov's avatar
Sergei Morozov committed
894 895 896
        $table->addColumn('id', 'integer', ['comment' => 'This is a comment']);
        $table->addColumn('date_interval', 'dateinterval', ['comment' => 'This is a comment']);
        $table->setPrimaryKey(['id']);
897

Sergei Morozov's avatar
Sergei Morozov committed
898
        $this->schemaManager->createTable($table);
899

Sergei Morozov's avatar
Sergei Morozov committed
900
        $columns = $this->schemaManager->listTableColumns('column_dateinterval_comment');
901 902
        self::assertEquals(2, count($columns));
        self::assertEquals('This is a comment', $columns['id']->getComment());
Sergei Morozov's avatar
Sergei Morozov committed
903
        self::assertEquals('This is a comment', $columns['date_interval']->getComment(), 'The Doctrine2 Typehint should be stripped from comment.');
Sergei Morozov's avatar
Sergei Morozov committed
904
        self::assertInstanceOf(DateIntervalType::class, $columns['date_interval']->getType(), 'The Doctrine2 should be detected from comment hint.');
905 906
    }

907 908 909
    /**
     * @group DBAL-825
     */
910
    public function testChangeColumnsTypeWithDefaultValue() : void
911 912 913 914
    {
        $tableName = 'column_def_change_type';
        $table     = new Table($tableName);

Sergei Morozov's avatar
Sergei Morozov committed
915 916
        $table->addColumn('col_int', 'smallint', ['default' => 666]);
        $table->addColumn('col_string', 'string', ['default' => 'foo']);
917

Sergei Morozov's avatar
Sergei Morozov committed
918
        $this->schemaManager->dropAndCreateTable($table);
919

Sergei Morozov's avatar
Sergei Morozov committed
920 921
        $tableDiff                            = new TableDiff($tableName);
        $tableDiff->fromTable                 = $table;
922 923
        $tableDiff->changedColumns['col_int'] = new ColumnDiff(
            'col_int',
Sergei Morozov's avatar
Sergei Morozov committed
924 925 926
            new Column('col_int', Type::getType('integer'), ['default' => 666]),
            ['type'],
            new Column('col_int', Type::getType('smallint'), ['default' => 666])
927 928 929 930
        );

        $tableDiff->changedColumns['col_string'] = new ColumnDiff(
            'col_string',
Sergei Morozov's avatar
Sergei Morozov committed
931 932 933
            new Column('col_string', Type::getType('string'), ['default' => 'foo', 'fixed' => true]),
            ['fixed'],
            new Column('col_string', Type::getType('string'), ['default' => 'foo'])
934 935
        );

Sergei Morozov's avatar
Sergei Morozov committed
936
        $this->schemaManager->alterTable($tableDiff);
937

Sergei Morozov's avatar
Sergei Morozov committed
938
        $columns = $this->schemaManager->listTableColumns($tableName);
939

Sergei Morozov's avatar
Sergei Morozov committed
940
        self::assertInstanceOf(IntegerType::class, $columns['col_int']->getType());
941
        self::assertEquals(666, $columns['col_int']->getDefault());
942

Sergei Morozov's avatar
Sergei Morozov committed
943
        self::assertInstanceOf(StringType::class, $columns['col_string']->getType());
944
        self::assertEquals('foo', $columns['col_string']->getDefault());
945 946
    }

947 948 949
    /**
     * @group DBAL-197
     */
950
    public function testListTableWithBlob() : void
951
    {
952
        $table = new Table('test_blob_table');
Luís Cobucci's avatar
Luís Cobucci committed
953 954 955
        $table->addColumn('id', 'integer', ['comment' => 'This is a comment']);
        $table->addColumn('binarydata', 'blob', []);
        $table->setPrimaryKey(['id']);
956

Sergei Morozov's avatar
Sergei Morozov committed
957
        $this->schemaManager->createTable($table);
Luís Cobucci's avatar
Luís Cobucci committed
958

Sergei Morozov's avatar
Sergei Morozov committed
959
        $created = $this->schemaManager->listTableDetails('test_blob_table');
Luís Cobucci's avatar
Luís Cobucci committed
960 961 962 963

        self::assertTrue($created->hasColumn('id'));
        self::assertTrue($created->hasColumn('binarydata'));
        self::assertTrue($created->hasPrimaryKey());
964 965
    }

966
    /**
Sergei Morozov's avatar
Sergei Morozov committed
967
     * @param mixed[] $data
968
     */
969
    protected function createTestTable(string $name = 'test_table', array $data = []) : Table
970
    {
971
        $options = $data['options'] ?? [];
972

973 974
        $table = $this->getTestTable($name, $options);

Sergei Morozov's avatar
Sergei Morozov committed
975
        $this->schemaManager->dropAndCreateTable($table);
976 977

        return $table;
978 979
    }

980 981 982 983
    /**
     * @param mixed[] $options
     */
    protected function getTestTable(string $name, array $options = []) : Table
984
    {
Sergei Morozov's avatar
Sergei Morozov committed
985
        $table = new Table($name, [], [], [], false, $options);
Sergei Morozov's avatar
Sergei Morozov committed
986
        $table->setSchemaConfig($this->schemaManager->createSchemaConfig());
Sergei Morozov's avatar
Sergei Morozov committed
987 988 989
        $table->addColumn('id', 'integer', ['notnull' => true]);
        $table->setPrimaryKey(['id']);
        $table->addColumn('test', 'string', ['length' => 255]);
990
        $table->addColumn('foreign_key_test', 'integer');
991

992 993 994
        return $table;
    }

995
    protected function getTestCompositeTable(string $name) : Table
996
    {
Sergei Morozov's avatar
Sergei Morozov committed
997
        $table = new Table($name, [], [], [], false, []);
Sergei Morozov's avatar
Sergei Morozov committed
998
        $table->setSchemaConfig($this->schemaManager->createSchemaConfig());
Sergei Morozov's avatar
Sergei Morozov committed
999 1000 1001 1002
        $table->addColumn('id', 'integer', ['notnull' => true]);
        $table->addColumn('other_id', 'integer', ['notnull' => true]);
        $table->setPrimaryKey(['id', 'other_id']);
        $table->addColumn('test', 'string', ['length' => 255]);
1003

1004
        return $table;
1005
    }
1006

1007 1008 1009 1010
    /**
     * @param Table[] $tables
     */
    protected function assertHasTable(array $tables) : void
1011 1012
    {
        $foundTable = false;
jeroendedauw's avatar
jeroendedauw committed
1013
        foreach ($tables as $table) {
Sergei Morozov's avatar
Sergei Morozov committed
1014
            self::assertInstanceOf(Table::class, $table, 'No Table instance was found in tables array.');
Sergei Morozov's avatar
Sergei Morozov committed
1015 1016
            if (strtolower($table->getName()) !== 'list_tables_test_new_name') {
                continue;
1017
            }
Sergei Morozov's avatar
Sergei Morozov committed
1018 1019

            $foundTable = true;
1020
        }
Sergei Morozov's avatar
Sergei Morozov committed
1021
        self::assertTrue($foundTable, 'Could not find new table');
1022
    }
1023

1024
    public function testListForeignKeysComposite() : void
1025
    {
Sergei Morozov's avatar
Sergei Morozov committed
1026
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
1027 1028 1029
            $this->markTestSkipped('Does not support foreign key constraints.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
1030 1031
        $this->schemaManager->createTable($this->getTestTable('test_create_fk3'));
        $this->schemaManager->createTable($this->getTestCompositeTable('test_create_fk4'));
1032

Sergei Morozov's avatar
Sergei Morozov committed
1033 1034 1035 1036 1037
        $foreignKey = new ForeignKeyConstraint(
            ['id', 'foreign_key_test'],
            'test_create_fk4',
            ['id', 'other_id'],
            'foreign_key_test_fk2'
1038 1039
        );

Sergei Morozov's avatar
Sergei Morozov committed
1040
        $this->schemaManager->createForeignKey($foreignKey, 'test_create_fk3');
1041

Sergei Morozov's avatar
Sergei Morozov committed
1042
        $fkeys = $this->schemaManager->listTableForeignKeys('test_create_fk3');
1043

1044
        self::assertEquals(1, count($fkeys), "Table 'test_create_fk3' has to have one foreign key.");
1045

Sergei Morozov's avatar
Sergei Morozov committed
1046
        self::assertInstanceOf(ForeignKeyConstraint::class, $fkeys[0]);
Sergei Morozov's avatar
Sergei Morozov committed
1047 1048
        self::assertEquals(['id', 'foreign_key_test'], array_map('strtolower', $fkeys[0]->getLocalColumns()));
        self::assertEquals(['id', 'other_id'], array_map('strtolower', $fkeys[0]->getForeignColumns()));
1049
    }
1050 1051 1052 1053

    /**
     * @group DBAL-44
     */
1054
    public function testColumnDefaultLifecycle() : void
1055
    {
Sergei Morozov's avatar
Sergei Morozov committed
1056 1057 1058 1059 1060 1061 1062 1063 1064 1065
        $table = new Table('col_def_lifecycle');
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
        $table->addColumn('column1', 'string', ['default' => null]);
        $table->addColumn('column2', 'string', ['default' => false]);
        $table->addColumn('column3', 'string', ['default' => true]);
        $table->addColumn('column4', 'string', ['default' => 0]);
        $table->addColumn('column5', 'string', ['default' => '']);
        $table->addColumn('column6', 'string', ['default' => 'def']);
        $table->addColumn('column7', 'integer', ['default' => 0]);
        $table->setPrimaryKey(['id']);
1066

Sergei Morozov's avatar
Sergei Morozov committed
1067
        $this->schemaManager->dropAndCreateTable($table);
1068

Sergei Morozov's avatar
Sergei Morozov committed
1069
        $columns = $this->schemaManager->listTableColumns('col_def_lifecycle');
1070

1071 1072 1073 1074 1075 1076 1077 1078
        self::assertNull($columns['id']->getDefault());
        self::assertNull($columns['column1']->getDefault());
        self::assertSame('', $columns['column2']->getDefault());
        self::assertSame('1', $columns['column3']->getDefault());
        self::assertSame('0', $columns['column4']->getDefault());
        self::assertSame('', $columns['column5']->getDefault());
        self::assertSame('def', $columns['column6']->getDefault());
        self::assertSame('0', $columns['column7']->getDefault());
1079 1080 1081

        $diffTable = clone $table;

Sergei Morozov's avatar
Sergei Morozov committed
1082 1083 1084 1085 1086 1087 1088
        $diffTable->changeColumn('column1', ['default' => false]);
        $diffTable->changeColumn('column2', ['default' => null]);
        $diffTable->changeColumn('column3', ['default' => false]);
        $diffTable->changeColumn('column4', ['default' => null]);
        $diffTable->changeColumn('column5', ['default' => false]);
        $diffTable->changeColumn('column6', ['default' => 666]);
        $diffTable->changeColumn('column7', ['default' => null]);
1089 1090 1091

        $comparator = new Comparator();

Sergei Morozov's avatar
Sergei Morozov committed
1092
        $this->schemaManager->alterTable($comparator->diffTable($table, $diffTable));
1093

Sergei Morozov's avatar
Sergei Morozov committed
1094
        $columns = $this->schemaManager->listTableColumns('col_def_lifecycle');
1095

1096 1097 1098 1099 1100 1101 1102
        self::assertSame('', $columns['column1']->getDefault());
        self::assertNull($columns['column2']->getDefault());
        self::assertSame('', $columns['column3']->getDefault());
        self::assertNull($columns['column4']->getDefault());
        self::assertSame('', $columns['column5']->getDefault());
        self::assertSame('666', $columns['column6']->getDefault());
        self::assertNull($columns['column7']->getDefault());
1103
    }
Steve Müller's avatar
Steve Müller committed
1104

1105
    public function testListTableWithBinary() : void
Steve Müller's avatar
Steve Müller committed
1106 1107 1108
    {
        $tableName = 'test_binary_table';

1109
        $table = new Table($tableName);
Steve Müller's avatar
Steve Müller committed
1110
        $table->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
1111 1112 1113
        $table->addColumn('column_varbinary', 'binary', []);
        $table->addColumn('column_binary', 'binary', ['fixed' => true]);
        $table->setPrimaryKey(['id']);
Steve Müller's avatar
Steve Müller committed
1114

Sergei Morozov's avatar
Sergei Morozov committed
1115
        $this->schemaManager->createTable($table);
Steve Müller's avatar
Steve Müller committed
1116

Sergei Morozov's avatar
Sergei Morozov committed
1117
        $table = $this->schemaManager->listTableDetails($tableName);
Steve Müller's avatar
Steve Müller committed
1118

Sergei Morozov's avatar
Sergei Morozov committed
1119
        self::assertInstanceOf(BinaryType::class, $table->getColumn('column_varbinary')->getType());
1120
        self::assertFalse($table->getColumn('column_varbinary')->getFixed());
Steve Müller's avatar
Steve Müller committed
1121

Sergei Morozov's avatar
Sergei Morozov committed
1122
        self::assertInstanceOf(BinaryType::class, $table->getColumn('column_binary')->getType());
1123
        self::assertTrue($table->getColumn('column_binary')->getFixed());
Steve Müller's avatar
Steve Müller committed
1124
    }
1125

1126
    public function testListTableDetailsWithFullQualifiedTableName() : void
1127
    {
Sergei Morozov's avatar
Sergei Morozov committed
1128
        if (! $this->schemaManager->getDatabasePlatform()->supportsSchemas()) {
1129 1130 1131
            $this->markTestSkipped('Test only works on platforms that support schemas.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
1132
        $defaultSchemaName = $this->schemaManager->getDatabasePlatform()->getDefaultSchemaName();
1133 1134 1135 1136
        $primaryTableName  = 'primary_table';
        $foreignTableName  = 'foreign_table';

        $table = new Table($foreignTableName);
Sergei Morozov's avatar
Sergei Morozov committed
1137 1138
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
        $table->setPrimaryKey(['id']);
1139

Sergei Morozov's avatar
Sergei Morozov committed
1140
        $this->schemaManager->dropAndCreateTable($table);
1141 1142

        $table = new Table($primaryTableName);
Sergei Morozov's avatar
Sergei Morozov committed
1143
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
1144 1145
        $table->addColumn('foo', 'integer');
        $table->addColumn('bar', 'string');
Sergei Morozov's avatar
Sergei Morozov committed
1146 1147 1148
        $table->addForeignKeyConstraint($foreignTableName, ['foo'], ['id']);
        $table->addIndex(['bar']);
        $table->setPrimaryKey(['id']);
1149

Sergei Morozov's avatar
Sergei Morozov committed
1150
        $this->schemaManager->dropAndCreateTable($table);
1151

1152
        self::assertEquals(
Sergei Morozov's avatar
Sergei Morozov committed
1153 1154
            $this->schemaManager->listTableColumns($primaryTableName),
            $this->schemaManager->listTableColumns($defaultSchemaName . '.' . $primaryTableName)
1155
        );
1156
        self::assertEquals(
Sergei Morozov's avatar
Sergei Morozov committed
1157 1158
            $this->schemaManager->listTableIndexes($primaryTableName),
            $this->schemaManager->listTableIndexes($defaultSchemaName . '.' . $primaryTableName)
1159
        );
1160
        self::assertEquals(
Sergei Morozov's avatar
Sergei Morozov committed
1161 1162
            $this->schemaManager->listTableForeignKeys($primaryTableName),
            $this->schemaManager->listTableForeignKeys($defaultSchemaName . '.' . $primaryTableName)
1163 1164
        );
    }
1165

1166
    public function testCommentStringsAreQuoted() : void
1167
    {
Sergei Morozov's avatar
Sergei Morozov committed
1168 1169 1170
        if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() &&
            ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() &&
            $this->connection->getDatabasePlatform()->getName() !== 'mssql') {
1171 1172 1173 1174
            $this->markTestSkipped('Database does not support column comments.');
        }

        $table = new Table('my_table');
Sergei Morozov's avatar
Sergei Morozov committed
1175 1176
        $table->addColumn('id', 'integer', ['comment' => "It's a comment with a quote"]);
        $table->setPrimaryKey(['id']);
1177

Sergei Morozov's avatar
Sergei Morozov committed
1178
        $this->schemaManager->createTable($table);
1179

Sergei Morozov's avatar
Sergei Morozov committed
1180
        $columns = $this->schemaManager->listTableColumns('my_table');
1181
        self::assertEquals("It's a comment with a quote", $columns['id']->getComment());
1182
    }
1183

1184
    public function testCommentNotDuplicated() : void
1185
    {
Sergei Morozov's avatar
Sergei Morozov committed
1186
        if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments()) {
1187 1188 1189
            $this->markTestSkipped('Database does not support column comments.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
1190
        $options          = [
1191 1192
            'type' => Type::getType('integer'),
            'default' => 0,
1193
            'notnull' => true,
1194
            'comment' => 'expected+column+comment',
Sergei Morozov's avatar
Sergei Morozov committed
1195
        ];
Sergei Morozov's avatar
Sergei Morozov committed
1196
        $columnDefinition = substr($this->connection->getDatabasePlatform()->getColumnDeclarationSQL('id', $options), strlen('id') + 1);
1197 1198

        $table = new Table('my_table');
Sergei Morozov's avatar
Sergei Morozov committed
1199
        $table->addColumn('id', 'integer', ['columnDefinition' => $columnDefinition, 'comment' => 'unexpected_column_comment']);
Sergei Morozov's avatar
Sergei Morozov committed
1200
        $sql = $this->connection->getDatabasePlatform()->getCreateTableSQL($table);
1201

1202 1203
        self::assertStringContainsString('expected+column+comment', $sql[0]);
        self::assertStringNotContainsString('unexpected_column_comment', $sql[0]);
1204 1205
    }

1206 1207 1208 1209
    /**
     * @group DBAL-1009
     * @dataProvider getAlterColumnComment
     */
1210 1211 1212 1213 1214 1215
    public function testAlterColumnComment(
        ?string $comment1,
        ?string $expectedComment1,
        ?string $comment2,
        ?string $expectedComment2
    ) : void {
Sergei Morozov's avatar
Sergei Morozov committed
1216 1217 1218
        if (! $this->connection->getDatabasePlatform()->supportsInlineColumnComments() &&
            ! $this->connection->getDatabasePlatform()->supportsCommentOnStatement() &&
            $this->connection->getDatabasePlatform()->getName() !== 'mssql') {
1219 1220 1221 1222
            $this->markTestSkipped('Database does not support column comments.');
        }

        $offlineTable = new Table('alter_column_comment_test');
Sergei Morozov's avatar
Sergei Morozov committed
1223 1224
        $offlineTable->addColumn('comment1', 'integer', ['comment' => $comment1]);
        $offlineTable->addColumn('comment2', 'integer', ['comment' => $comment2]);
1225 1226
        $offlineTable->addColumn('no_comment1', 'integer');
        $offlineTable->addColumn('no_comment2', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
1227
        $this->schemaManager->dropAndCreateTable($offlineTable);
1228

Sergei Morozov's avatar
Sergei Morozov committed
1229
        $onlineTable = $this->schemaManager->listTableDetails('alter_column_comment_test');
1230

1231 1232 1233 1234
        self::assertSame($expectedComment1, $onlineTable->getColumn('comment1')->getComment());
        self::assertSame($expectedComment2, $onlineTable->getColumn('comment2')->getComment());
        self::assertNull($onlineTable->getColumn('no_comment1')->getComment());
        self::assertNull($onlineTable->getColumn('no_comment2')->getComment());
1235

Sergei Morozov's avatar
Sergei Morozov committed
1236 1237 1238 1239
        $onlineTable->changeColumn('comment1', ['comment' => $comment2]);
        $onlineTable->changeColumn('comment2', ['comment' => $comment1]);
        $onlineTable->changeColumn('no_comment1', ['comment' => $comment1]);
        $onlineTable->changeColumn('no_comment2', ['comment' => $comment2]);
1240 1241 1242 1243 1244

        $comparator = new Comparator();

        $tableDiff = $comparator->diffTable($offlineTable, $onlineTable);

Sergei Morozov's avatar
Sergei Morozov committed
1245
        self::assertInstanceOf(TableDiff::class, $tableDiff);
1246

Sergei Morozov's avatar
Sergei Morozov committed
1247
        $this->schemaManager->alterTable($tableDiff);
1248

Sergei Morozov's avatar
Sergei Morozov committed
1249
        $onlineTable = $this->schemaManager->listTableDetails('alter_column_comment_test');
1250

1251 1252 1253 1254
        self::assertSame($expectedComment2, $onlineTable->getColumn('comment1')->getComment());
        self::assertSame($expectedComment1, $onlineTable->getColumn('comment2')->getComment());
        self::assertSame($expectedComment1, $onlineTable->getColumn('no_comment1')->getComment());
        self::assertSame($expectedComment2, $onlineTable->getColumn('no_comment2')->getComment());
1255 1256
    }

1257 1258 1259 1260
    /**
     * @return mixed[][]
     */
    public static function getAlterColumnComment() : iterable
1261
    {
Sergei Morozov's avatar
Sergei Morozov committed
1262 1263 1264 1265
        return [
            [null, null, ' ', ' '],
            [null, null, '0', '0'],
            [null, null, 'foo', 'foo'],
1266

Sergei Morozov's avatar
Sergei Morozov committed
1267 1268 1269
            ['', null, ' ', ' '],
            ['', null, '0', '0'],
            ['', null, 'foo', 'foo'],
1270

Sergei Morozov's avatar
Sergei Morozov committed
1271 1272
            [' ', ' ', '0', '0'],
            [' ', ' ', 'foo', 'foo'],
1273

Sergei Morozov's avatar
Sergei Morozov committed
1274 1275
            ['0', '0', 'foo', 'foo'],
        ];
1276
    }
1277 1278 1279 1280

    /**
     * @group DBAL-1095
     */
1281
    public function testDoesNotListIndexesImplicitlyCreatedByForeignKeys() : void
1282
    {
Sergei Morozov's avatar
Sergei Morozov committed
1283
        if (! $this->schemaManager->getDatabasePlatform()->supportsForeignKeyConstraints()) {
1284 1285 1286
            $this->markTestSkipped('This test is only supported on platforms that have foreign keys.');
        }

1287
        $primaryTable = new Table('test_list_index_impl_primary');
1288
        $primaryTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
1289
        $primaryTable->setPrimaryKey(['id']);
1290

1291
        $foreignTable = new Table('test_list_index_impl_foreign');
1292 1293
        $foreignTable->addColumn('fk1', 'integer');
        $foreignTable->addColumn('fk2', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
1294 1295 1296
        $foreignTable->addIndex(['fk1'], 'explicit_fk1_idx');
        $foreignTable->addForeignKeyConstraint('test_list_index_impl_primary', ['fk1'], ['id']);
        $foreignTable->addForeignKeyConstraint('test_list_index_impl_primary', ['fk2'], ['id']);
1297

Sergei Morozov's avatar
Sergei Morozov committed
1298 1299
        $this->schemaManager->dropAndCreateTable($primaryTable);
        $this->schemaManager->dropAndCreateTable($foreignTable);
1300

Sergei Morozov's avatar
Sergei Morozov committed
1301
        $indexes = $this->schemaManager->listTableIndexes('test_list_index_impl_foreign');
1302

1303 1304 1305
        self::assertCount(2, $indexes);
        self::assertArrayHasKey('explicit_fk1_idx', $indexes);
        self::assertArrayHasKey('idx_3d6c147fdc58d6c', $indexes);
1306
    }
1307 1308 1309 1310 1311 1312

    /**
     * @after
     */
    public function removeJsonArrayTable() : void
    {
Sergei Morozov's avatar
Sergei Morozov committed
1313
        if (! $this->schemaManager->tablesExist(['json_array_test'])) {
Sergei Morozov's avatar
Sergei Morozov committed
1314
            return;
1315
        }
Sergei Morozov's avatar
Sergei Morozov committed
1316

Sergei Morozov's avatar
Sergei Morozov committed
1317
        $this->schemaManager->dropTable('json_array_test');
1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328
    }

    /**
     * @group 2782
     * @group 6654
     */
    public function testComparatorShouldReturnFalseWhenLegacyJsonArrayColumnHasComment() : void
    {
        $table = new Table('json_array_test');
        $table->addColumn('parameters', 'json_array');

Sergei Morozov's avatar
Sergei Morozov committed
1329
        $this->schemaManager->createTable($table);
1330 1331

        $comparator = new Comparator();
Sergei Morozov's avatar
Sergei Morozov committed
1332
        $tableDiff  = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table);
1333 1334 1335 1336 1337 1338 1339 1340 1341 1342

        self::assertFalse($tableDiff);
    }

    /**
     * @group 2782
     * @group 6654
     */
    public function testComparatorShouldModifyOnlyTheCommentWhenUpdatingFromJsonArrayTypeOnLegacyPlatforms() : void
    {
Sergei Morozov's avatar
Sergei Morozov committed
1343
        if ($this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) {
1344 1345 1346 1347 1348 1349
            $this->markTestSkipped('This test is only supported on platforms that do not have native JSON type.');
        }

        $table = new Table('json_array_test');
        $table->addColumn('parameters', 'json_array');

Sergei Morozov's avatar
Sergei Morozov committed
1350
        $this->schemaManager->createTable($table);
1351 1352 1353 1354 1355

        $table = new Table('json_array_test');
        $table->addColumn('parameters', 'json');

        $comparator = new Comparator();
Sergei Morozov's avatar
Sergei Morozov committed
1356
        $tableDiff  = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table);
1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370

        self::assertInstanceOf(TableDiff::class, $tableDiff);

        $changedColumn = $tableDiff->changedColumns['parameters'] ?? $tableDiff->changedColumns['PARAMETERS'];

        self::assertSame(['comment'], $changedColumn->changedProperties);
    }

    /**
     * @group 2782
     * @group 6654
     */
    public function testComparatorShouldAddCommentToLegacyJsonArrayTypeThatDoesNotHaveIt() : void
    {
Sergei Morozov's avatar
Sergei Morozov committed
1371
        if (! $this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) {
1372 1373 1374
            $this->markTestSkipped('This test is only supported on platforms that have native JSON type.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
1375
        $this->connection->executeQuery('CREATE TABLE json_array_test (parameters JSON NOT NULL)');
1376 1377 1378 1379 1380

        $table = new Table('json_array_test');
        $table->addColumn('parameters', 'json_array');

        $comparator = new Comparator();
Sergei Morozov's avatar
Sergei Morozov committed
1381
        $tableDiff  = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table);
1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392

        self::assertInstanceOf(TableDiff::class, $tableDiff);
        self::assertSame(['comment'], $tableDiff->changedColumns['parameters']->changedProperties);
    }

    /**
     * @group 2782
     * @group 6654
     */
    public function testComparatorShouldReturnAllChangesWhenUsingLegacyJsonArrayType() : void
    {
Sergei Morozov's avatar
Sergei Morozov committed
1393
        if (! $this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) {
1394 1395 1396
            $this->markTestSkipped('This test is only supported on platforms that have native JSON type.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
1397
        $this->connection->executeQuery('CREATE TABLE json_array_test (parameters JSON DEFAULT NULL)');
1398 1399 1400 1401 1402

        $table = new Table('json_array_test');
        $table->addColumn('parameters', 'json_array');

        $comparator = new Comparator();
Sergei Morozov's avatar
Sergei Morozov committed
1403
        $tableDiff  = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table);
1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414

        self::assertInstanceOf(TableDiff::class, $tableDiff);
        self::assertSame(['notnull', 'comment'], $tableDiff->changedColumns['parameters']->changedProperties);
    }

    /**
     * @group 2782
     * @group 6654
     */
    public function testComparatorShouldReturnAllChangesWhenUsingLegacyJsonArrayTypeEvenWhenPlatformHasJsonSupport() : void
    {
Sergei Morozov's avatar
Sergei Morozov committed
1415
        if (! $this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) {
1416 1417 1418
            $this->markTestSkipped('This test is only supported on platforms that have native JSON type.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
1419
        $this->connection->executeQuery('CREATE TABLE json_array_test (parameters JSON DEFAULT NULL)');
1420 1421 1422 1423 1424

        $table = new Table('json_array_test');
        $table->addColumn('parameters', 'json_array');

        $comparator = new Comparator();
Sergei Morozov's avatar
Sergei Morozov committed
1425
        $tableDiff  = $comparator->diffTable($this->schemaManager->listTableDetails('json_array_test'), $table);
1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436

        self::assertInstanceOf(TableDiff::class, $tableDiff);
        self::assertSame(['notnull', 'comment'], $tableDiff->changedColumns['parameters']->changedProperties);
    }

    /**
     * @group 2782
     * @group 6654
     */
    public function testComparatorShouldNotAddCommentToJsonTypeSinceItIsTheDefaultNow() : void
    {
Sergei Morozov's avatar
Sergei Morozov committed
1437
        if (! $this->schemaManager->getDatabasePlatform()->hasNativeJsonType()) {
1438 1439 1440
            $this->markTestSkipped('This test is only supported on platforms that have native JSON type.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
1441
        $this->connection->executeQuery('CREATE TABLE json_test (parameters JSON NOT NULL)');
1442 1443 1444 1445 1446

        $table = new Table('json_test');
        $table->addColumn('parameters', 'json');

        $comparator = new Comparator();
Sergei Morozov's avatar
Sergei Morozov committed
1447
        $tableDiff  = $comparator->diffTable($this->schemaManager->listTableDetails('json_test'), $table);
1448 1449 1450

        self::assertFalse($tableDiff);
    }
1451 1452 1453 1454 1455 1456 1457

    /**
     * @dataProvider commentsProvider
     * @group 2596
     */
    public function testExtractDoctrineTypeFromComment(string $comment, string $expected, string $currentType) : void
    {
Sergei Morozov's avatar
Sergei Morozov committed
1458
        $result = $this->schemaManager->extractDoctrineTypeFromComment($comment, $currentType);
1459 1460 1461 1462

        self::assertSame($expected, $result);
    }

Sergei Morozov's avatar
Sergei Morozov committed
1463 1464 1465
    /**
     * @return string[][]
     */
1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478
    public function commentsProvider() : array
    {
        $currentType = 'current type';

        return [
            'invalid custom type comments'      => ['should.return.current.type', $currentType, $currentType],
            'valid doctrine type'               => ['(DC2Type:guid)', 'guid', $currentType],
            'valid with dots'                   => ['(DC2Type:type.should.return)', 'type.should.return', $currentType],
            'valid with namespace'              => ['(DC2Type:Namespace\Class)', 'Namespace\Class', $currentType],
            'valid with extra closing bracket'  => ['(DC2Type:should.stop)).before)', 'should.stop', $currentType],
            'valid with extra opening brackets' => ['(DC2Type:should((.stop)).before)', 'should((.stop', $currentType],
        ];
    }
1479 1480 1481

    public function testCreateAndListSequences() : void
    {
Sergei Morozov's avatar
Sergei Morozov committed
1482
        if (! $this->schemaManager->getDatabasePlatform()->supportsSequences()) {
1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494
            self::markTestSkipped('This test is only supported on platforms that support sequences.');
        }

        $sequence1Name           = 'sequence_1';
        $sequence1AllocationSize = 1;
        $sequence1InitialValue   = 2;
        $sequence2Name           = 'sequence_2';
        $sequence2AllocationSize = 3;
        $sequence2InitialValue   = 4;
        $sequence1               = new Sequence($sequence1Name, $sequence1AllocationSize, $sequence1InitialValue);
        $sequence2               = new Sequence($sequence2Name, $sequence2AllocationSize, $sequence2InitialValue);

Sergei Morozov's avatar
Sergei Morozov committed
1495 1496
        $this->schemaManager->createSequence($sequence1);
        $this->schemaManager->createSequence($sequence2);
1497 1498 1499

        /** @var Sequence[] $actualSequences */
        $actualSequences = [];
Sergei Morozov's avatar
Sergei Morozov committed
1500
        foreach ($this->schemaManager->listSequences() as $sequence) {
1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514
            $actualSequences[$sequence->getName()] = $sequence;
        }

        $actualSequence1 = $actualSequences[$sequence1Name];
        $actualSequence2 = $actualSequences[$sequence2Name];

        self::assertSame($sequence1Name, $actualSequence1->getName());
        self::assertEquals($sequence1AllocationSize, $actualSequence1->getAllocationSize());
        self::assertEquals($sequence1InitialValue, $actualSequence1->getInitialValue());

        self::assertSame($sequence2Name, $actualSequence2->getName());
        self::assertEquals($sequence2AllocationSize, $actualSequence2->getAllocationSize());
        self::assertEquals($sequence2InitialValue, $actualSequence2->getInitialValue());
    }
1515 1516 1517 1518 1519 1520

    /**
     * @group #3086
     */
    public function testComparisonWithAutoDetectedSequenceDefinition() : void
    {
Sergei Morozov's avatar
Sergei Morozov committed
1521
        if (! $this->schemaManager->getDatabasePlatform()->supportsSequences()) {
1522 1523 1524 1525 1526 1527 1528 1529
            self::markTestSkipped('This test is only supported on platforms that support sequences.');
        }

        $sequenceName           = 'sequence_auto_detect_test';
        $sequenceAllocationSize = 5;
        $sequenceInitialValue   = 10;
        $sequence               = new Sequence($sequenceName, $sequenceAllocationSize, $sequenceInitialValue);

Sergei Morozov's avatar
Sergei Morozov committed
1530
        $this->schemaManager->dropAndCreateSequence($sequence);
1531 1532 1533

        $createdSequence = array_values(
            array_filter(
Sergei Morozov's avatar
Sergei Morozov committed
1534
                $this->schemaManager->listSequences(),
Sergei Morozov's avatar
Sergei Morozov committed
1535
                static function (Sequence $sequence) use ($sequenceName) : bool {
1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547
                    return strcasecmp($sequence->getName(), $sequenceName) === 0;
                }
            )
        )[0] ?? null;

        self::assertNotNull($createdSequence);

        $comparator = new Comparator();
        $tableDiff  = $comparator->diffSequence($createdSequence, $sequence);

        self::assertFalse($tableDiff);
    }
Timo Bakx's avatar
Timo Bakx committed
1548 1549 1550 1551

    /**
     * @group DBAL-2921
     */
1552
    public function testPrimaryKeyAutoIncrement() : void
Timo Bakx's avatar
Timo Bakx committed
1553 1554 1555 1556 1557
    {
        $table = new Table('test_pk_auto_increment');
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
        $table->addColumn('text', 'string');
        $table->setPrimaryKey(['id']);
Sergei Morozov's avatar
Sergei Morozov committed
1558
        $this->schemaManager->dropAndCreateTable($table);
Timo Bakx's avatar
Timo Bakx committed
1559

Sergei Morozov's avatar
Sergei Morozov committed
1560
        $this->connection->insert('test_pk_auto_increment', ['text' => '1']);
Timo Bakx's avatar
Timo Bakx committed
1561

Sergei Morozov's avatar
Sergei Morozov committed
1562
        $query = $this->connection->query('SELECT id FROM test_pk_auto_increment WHERE text = \'1\'');
Timo Bakx's avatar
Timo Bakx committed
1563 1564 1565
        $query->execute();
        $lastUsedIdBeforeDelete = (int) $query->fetchColumn();

Sergei Morozov's avatar
Sergei Morozov committed
1566
        $this->connection->query('DELETE FROM test_pk_auto_increment');
Timo Bakx's avatar
Timo Bakx committed
1567

Sergei Morozov's avatar
Sergei Morozov committed
1568
        $this->connection->insert('test_pk_auto_increment', ['text' => '2']);
Timo Bakx's avatar
Timo Bakx committed
1569

Sergei Morozov's avatar
Sergei Morozov committed
1570
        $query = $this->connection->query('SELECT id FROM test_pk_auto_increment WHERE text = \'2\'');
Timo Bakx's avatar
Timo Bakx committed
1571 1572 1573 1574 1575
        $query->execute();
        $lastUsedIdAfterDelete = (int) $query->fetchColumn();

        $this->assertGreaterThan($lastUsedIdBeforeDelete, $lastUsedIdAfterDelete);
    }
1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595

    public function testGenerateAnIndexWithPartialColumnLength() : void
    {
        if (! $this->schemaManager->getDatabasePlatform()->supportsColumnLengthIndexes()) {
            self::markTestSkipped('This test is only supported on platforms that support indexes with column length definitions.');
        }

        $table = new Table('test_partial_column_index');
        $table->addColumn('long_column', 'string', ['length' => 40]);
        $table->addColumn('standard_column', 'integer');
        $table->addIndex(['long_column'], 'partial_long_column_idx', [], ['lengths' => [4]]);
        $table->addIndex(['standard_column', 'long_column'], 'standard_and_partial_idx', [], ['lengths' => [null, 2]]);

        $expected = $table->getIndexes();

        $this->schemaManager->dropAndCreateTable($table);

        $onlineTable = $this->schemaManager->listTableDetails('test_partial_column_index');
        self::assertEquals($expected, $onlineTable->getIndexes());
    }
1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606

    public function testCommentInTable() : void
    {
        $table = new Table('table_with_comment');
        $table->addColumn('id', 'integer');
        $table->setComment('Foo with control characters \'\\');
        $this->schemaManager->dropAndCreateTable($table);

        $table = $this->schemaManager->listTableDetails('table_with_comment');
        self::assertSame('Foo with control characters \'\\', $table->getComment());
    }
1607
}