<?php

namespace Doctrine\Tests\DBAL\Schema;

use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ColumnDiff;
use Doctrine\DBAL\Schema\Comparator;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaConfig;
use Doctrine\DBAL\Schema\SchemaDiff;
use Doctrine\DBAL\Schema\Sequence;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\Types\Type;
use PHPUnit\Framework\TestCase;
use function array_keys;

class ComparatorTest extends TestCase
{
    public function testCompareSame1()
    {
        $schema1 = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield1' => new Column('integerfield1', Type::getType('integer')),
                ]
            ),
        ]);
        $schema2 = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield1' => new Column('integerfield1', Type::getType('integer')),
                ]
            ),
        ]);

        $expected             = new SchemaDiff();
        $expected->fromSchema = $schema1;
        self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2));
    }

    public function testCompareSame2()
    {
        $schema1 = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield1' => new Column('integerfield1', Type::getType('integer')),
                    'integerfield2' => new Column('integerfield2', Type::getType('integer')),
                ]
            ),
        ]);
        $schema2 = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield2' => new Column('integerfield2', Type::getType('integer')),
                    'integerfield1' => new Column('integerfield1', Type::getType('integer')),
                ]
            ),
        ]);

        $expected             = new SchemaDiff();
        $expected->fromSchema = $schema1;
        self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2));
    }

    public function testCompareMissingTable()
    {
        $schemaConfig = new SchemaConfig();
        $table        = new Table('bugdb', ['integerfield1' => new Column('integerfield1', Type::getType('integer'))]);
        $table->setSchemaConfig($schemaConfig);

        $schema1 = new Schema([$table], [], $schemaConfig);
        $schema2 = new Schema([], [], $schemaConfig);

        $expected = new SchemaDiff([], [], ['bugdb' => $table], $schema1);

        self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2));
    }

    public function testCompareNewTable()
    {
        $schemaConfig = new SchemaConfig();
        $table        = new Table('bugdb', ['integerfield1' => new Column('integerfield1', Type::getType('integer'))]);
        $table->setSchemaConfig($schemaConfig);

        $schema1 = new Schema([], [], $schemaConfig);
        $schema2 = new Schema([$table], [], $schemaConfig);

        $expected = new SchemaDiff(['bugdb' => $table], [], [], $schema1);

        self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2));
    }

    public function testCompareOnlyAutoincrementChanged()
    {
        $column1 = new Column('foo', Type::getType('integer'), ['autoincrement' => true]);
        $column2 = new Column('foo', Type::getType('integer'), ['autoincrement' => false]);

        $comparator        = new Comparator();
        $changedProperties = $comparator->diffColumn($column1, $column2);

        self::assertEquals(['autoincrement'], $changedProperties);
    }

    public function testCompareMissingField()
    {
        $missingColumn = new Column('integerfield1', Type::getType('integer'));
        $schema1       = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield1' => $missingColumn,
                    'integerfield2' => new Column('integerfield2', Type::getType('integer')),
                ]
            ),
        ]);
        $schema2       = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield2' => new Column('integerfield2', Type::getType('integer')),
                ]
            ),
        ]);

        $expected                                    = new SchemaDiff(
            [],
            [
                'bugdb' => new TableDiff(
                    'bugdb',
                    [],
                    [],
                    ['integerfield1' => $missingColumn]
                ),
            ]
        );
        $expected->fromSchema                        = $schema1;
        $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb');

        self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2));
    }

    public function testCompareNewField()
    {
        $schema1 = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield1' => new Column('integerfield1', Type::getType('integer')),
                ]
            ),
        ]);
        $schema2 = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield1' => new Column('integerfield1', Type::getType('integer')),
                    'integerfield2' => new Column('integerfield2', Type::getType('integer')),
                ]
            ),
        ]);

        $expected                                    = new SchemaDiff(
            [],
            [
                'bugdb' => new TableDiff(
                    'bugdb',
                    [
                        'integerfield2' => new Column('integerfield2', Type::getType('integer')),
                    ]
                ),
            ]
        );
        $expected->fromSchema                        = $schema1;
        $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb');

        self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2));
    }

    public function testCompareChangedColumnsChangeType()
    {
        $column1 = new Column('charfield1', Type::getType('string'));
        $column2 = new Column('charfield1', Type::getType('integer'));

        $c = new Comparator();
        self::assertEquals(['type'], $c->diffColumn($column1, $column2));
        self::assertEquals([], $c->diffColumn($column1, $column1));
    }

    public function testCompareChangedColumnsChangeCustomSchemaOption()
    {
        $column1 = new Column('charfield1', Type::getType('string'));
        $column2 = new Column('charfield1', Type::getType('string'));

        $column1->setCustomSchemaOption('foo', 'bar');
        $column2->setCustomSchemaOption('foo', 'bar');

        $column1->setCustomSchemaOption('foo1', 'bar1');
        $column2->setCustomSchemaOption('foo2', 'bar2');

        $c = new Comparator();
        self::assertEquals(['foo1', 'foo2'], $c->diffColumn($column1, $column2));
        self::assertEquals([], $c->diffColumn($column1, $column1));
    }

    public function testCompareChangeColumnsMultipleNewColumnsRename()
    {
        $tableA = new Table('foo');
        $tableA->addColumn('datefield1', 'datetime');

        $tableB = new Table('foo');
        $tableB->addColumn('new_datefield1', 'datetime');
        $tableB->addColumn('new_datefield2', 'datetime');

        $c         = new Comparator();
        $tableDiff = $c->diffTable($tableA, $tableB);

        self::assertCount(1, $tableDiff->renamedColumns, 'we should have one rename datefield1 => new_datefield1.');
        self::assertArrayHasKey('datefield1', $tableDiff->renamedColumns, "'datefield1' should be set to be renamed to new_datefield1");
        self::assertCount(1, $tableDiff->addedColumns, "'new_datefield2' should be added");
        self::assertArrayHasKey('new_datefield2', $tableDiff->addedColumns, "'new_datefield2' should be added, not created through renaming!");
        self::assertCount(0, $tableDiff->removedColumns, 'Nothing should be removed.');
        self::assertCount(0, $tableDiff->changedColumns, 'Nothing should be changed as all fields old & new have diff names.');
    }

    public function testCompareRemovedIndex()
    {
        $schema1 = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield1' => new Column('integerfield1', Type::getType('integer')),
                    'integerfield2' => new Column('integerfield2', Type::getType('integer')),
                ],
                [
                    'primary' => new Index(
                        'primary',
                        ['integerfield1'],
                        true
                    ),
                ]
            ),
        ]);
        $schema2 = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield1' => new Column('integerfield1', Type::getType('integer')),
                    'integerfield2' => new Column('integerfield2', Type::getType('integer')),
                ]
            ),
        ]);

        $expected                                    = new SchemaDiff(
            [],
            [
                'bugdb' => new TableDiff(
                    'bugdb',
                    [],
                    [],
                    [],
                    [],
                    [],
                    [
                        'primary' => new Index(
                            'primary',
                            ['integerfield1'],
                            true
                        ),
                    ]
                ),
            ]
        );
        $expected->fromSchema                        = $schema1;
        $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb');

        self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2));
    }

    public function testCompareNewIndex()
    {
        $schema1 = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield1' => new Column('integerfield1', Type::getType('integer')),
                    'integerfield2' => new Column('integerfield2', Type::getType('integer')),
                ]
            ),
        ]);
        $schema2 = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield1' => new Column('integerfield1', Type::getType('integer')),
                    'integerfield2' => new Column('integerfield2', Type::getType('integer')),
                ],
                [
                    'primary' => new Index(
                        'primary',
                        ['integerfield1'],
                        true
                    ),
                ]
            ),
        ]);

        $expected                                    = new SchemaDiff(
            [],
            [
                'bugdb' => new TableDiff(
                    'bugdb',
                    [],
                    [],
                    [],
                    [
                        'primary' => new Index(
                            'primary',
                            ['integerfield1'],
                            true
                        ),
                    ]
                ),
            ]
        );
        $expected->fromSchema                        = $schema1;
        $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb');

        self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2));
    }

    public function testCompareChangedIndex()
    {
        $schema1 = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield1' => new Column('integerfield1', Type::getType('integer')),
                    'integerfield2' => new Column('integerfield2', Type::getType('integer')),
                ],
                [
                    'primary' => new Index(
                        'primary',
                        ['integerfield1'],
                        true
                    ),
                ]
            ),
        ]);
        $schema2 = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield1' => new Column('integerfield1', Type::getType('integer')),
                    'integerfield2' => new Column('integerfield2', Type::getType('integer')),
                ],
                [
                    'primary' => new Index(
                        'primary',
                        ['integerfield1', 'integerfield2'],
                        true
                    ),
                ]
            ),
        ]);

        $expected                                    = new SchemaDiff(
            [],
            [
                'bugdb' => new TableDiff(
                    'bugdb',
                    [],
                    [],
                    [],
                    [],
                    [
                        'primary' => new Index(
                            'primary',
                            [
                                'integerfield1',
                                'integerfield2',
                            ],
                            true
                        ),
                    ]
                ),
            ]
        );
        $expected->fromSchema                        = $schema1;
        $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb');

        self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2));
    }

    public function testCompareChangedIndexFieldPositions()
    {
        $schema1 = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield1' => new Column('integerfield1', Type::getType('integer')),
                    'integerfield2' => new Column('integerfield2', Type::getType('integer')),
                ],
                [
                    'primary' => new Index('primary', ['integerfield1', 'integerfield2'], true),
                ]
            ),
        ]);
        $schema2 = new Schema([
            'bugdb' => new Table(
                'bugdb',
                [
                    'integerfield1' => new Column('integerfield1', Type::getType('integer')),
                    'integerfield2' => new Column('integerfield2', Type::getType('integer')),
                ],
                [
                    'primary' => new Index('primary', ['integerfield2', 'integerfield1'], true),
                ]
            ),
        ]);

        $expected                                    = new SchemaDiff(
            [],
            [
                'bugdb' => new TableDiff(
                    'bugdb',
                    [],
                    [],
                    [],
                    [],
                    [
                        'primary' => new Index('primary', ['integerfield2', 'integerfield1'], true),
                    ]
                ),
            ]
        );
        $expected->fromSchema                        = $schema1;
        $expected->changedTables['bugdb']->fromTable = $schema1->getTable('bugdb');

        self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2));
    }

    public function testCompareSequences()
    {
        $seq1 = new Sequence('foo', 1, 1);
        $seq2 = new Sequence('foo', 1, 2);
        $seq3 = new Sequence('foo', 2, 1);
        $seq4 = new Sequence('foo', '1', '1');

        $c = new Comparator();

        self::assertTrue($c->diffSequence($seq1, $seq2));
        self::assertTrue($c->diffSequence($seq1, $seq3));
        self::assertFalse($c->diffSequence($seq1, $seq4));
    }

    public function testRemovedSequence()
    {
        $schema1 = new Schema();
        $seq     = $schema1->createSequence('foo');

        $schema2 = new Schema();

        $c          = new Comparator();
        $diffSchema = $c->compare($schema1, $schema2);

        self::assertCount(1, $diffSchema->removedSequences);
        self::assertSame($seq, $diffSchema->removedSequences[0]);
    }

    public function testAddedSequence()
    {
        $schema1 = new Schema();

        $schema2 = new Schema();
        $seq     = $schema2->createSequence('foo');

        $c          = new Comparator();
        $diffSchema = $c->compare($schema1, $schema2);

        self::assertCount(1, $diffSchema->newSequences);
        self::assertSame($seq, $diffSchema->newSequences[0]);
    }

    public function testTableAddForeignKey()
    {
        $tableForeign = new Table('bar');
        $tableForeign->addColumn('id', 'integer');

        $table1 = new Table('foo');
        $table1->addColumn('fk', 'integer');

        $table2 = new Table('foo');
        $table2->addColumn('fk', 'integer');
        $table2->addForeignKeyConstraint($tableForeign, ['fk'], ['id']);

        $c         = new Comparator();
        $tableDiff = $c->diffTable($table1, $table2);

        self::assertInstanceOf(TableDiff::class, $tableDiff);
        self::assertCount(1, $tableDiff->addedForeignKeys);
    }

    public function testTableRemoveForeignKey()
    {
        $tableForeign = new Table('bar');
        $tableForeign->addColumn('id', 'integer');

        $table1 = new Table('foo');
        $table1->addColumn('fk', 'integer');

        $table2 = new Table('foo');
        $table2->addColumn('fk', 'integer');
        $table2->addForeignKeyConstraint($tableForeign, ['fk'], ['id']);

        $c         = new Comparator();
        $tableDiff = $c->diffTable($table2, $table1);

        self::assertInstanceOf(TableDiff::class, $tableDiff);
        self::assertCount(1, $tableDiff->removedForeignKeys);
    }

    public function testTableUpdateForeignKey()
    {
        $tableForeign = new Table('bar');
        $tableForeign->addColumn('id', 'integer');

        $table1 = new Table('foo');
        $table1->addColumn('fk', 'integer');
        $table1->addForeignKeyConstraint($tableForeign, ['fk'], ['id']);

        $table2 = new Table('foo');
        $table2->addColumn('fk', 'integer');
        $table2->addForeignKeyConstraint($tableForeign, ['fk'], ['id'], ['onUpdate' => 'CASCADE']);

        $c         = new Comparator();
        $tableDiff = $c->diffTable($table1, $table2);

        self::assertInstanceOf(TableDiff::class, $tableDiff);
        self::assertCount(1, $tableDiff->changedForeignKeys);
    }

    public function testMovedForeignKeyForeignTable()
    {
        $tableForeign = new Table('bar');
        $tableForeign->addColumn('id', 'integer');

        $tableForeign2 = new Table('bar2');
        $tableForeign2->addColumn('id', 'integer');

        $table1 = new Table('foo');
        $table1->addColumn('fk', 'integer');
        $table1->addForeignKeyConstraint($tableForeign, ['fk'], ['id']);

        $table2 = new Table('foo');
        $table2->addColumn('fk', 'integer');
        $table2->addForeignKeyConstraint($tableForeign2, ['fk'], ['id']);

        $c         = new Comparator();
        $tableDiff = $c->diffTable($table1, $table2);

        self::assertInstanceOf(TableDiff::class, $tableDiff);
        self::assertCount(1, $tableDiff->changedForeignKeys);
    }

    public function testTablesCaseInsensitive()
    {
        $schemaA = new Schema();
        $schemaA->createTable('foo');
        $schemaA->createTable('bAr');
        $schemaA->createTable('BAZ');
        $schemaA->createTable('new');

        $schemaB = new Schema();
        $schemaB->createTable('FOO');
        $schemaB->createTable('bar');
        $schemaB->createTable('Baz');
        $schemaB->createTable('old');

        $c    = new Comparator();
        $diff = $c->compare($schemaA, $schemaB);

        self::assertSchemaTableChangeCount($diff, 1, 0, 1);
    }

    public function testSequencesCaseInsensitive()
    {
        $schemaA = new Schema();
        $schemaA->createSequence('foo');
        $schemaA->createSequence('BAR');
        $schemaA->createSequence('Baz');
        $schemaA->createSequence('new');

        $schemaB = new Schema();
        $schemaB->createSequence('FOO');
        $schemaB->createSequence('Bar');
        $schemaB->createSequence('baz');
        $schemaB->createSequence('old');

        $c    = new Comparator();
        $diff = $c->compare($schemaA, $schemaB);

        self::assertSchemaSequenceChangeCount($diff, 1, 0, 1);
    }

    public function testCompareColumnCompareCaseInsensitive()
    {
        $tableA = new Table('foo');
        $tableA->addColumn('id', 'integer');

        $tableB = new Table('foo');
        $tableB->addColumn('ID', 'integer');

        $c         = new Comparator();
        $tableDiff = $c->diffTable($tableA, $tableB);

        self::assertFalse($tableDiff);
    }

    public function testCompareIndexBasedOnPropertiesNotName()
    {
        $tableA = new Table('foo');
        $tableA->addColumn('id', 'integer');
        $tableA->addIndex(['id'], 'foo_bar_idx');

        $tableB = new Table('foo');
        $tableB->addColumn('ID', 'integer');
        $tableB->addIndex(['id'], 'bar_foo_idx');

        $c                                        = new Comparator();
        $tableDiff                                = new TableDiff('foo');
        $tableDiff->fromTable                     = $tableA;
        $tableDiff->renamedIndexes['foo_bar_idx'] = new Index('bar_foo_idx', ['id']);

        self::assertEquals(
            $tableDiff,
            $c->diffTable($tableA, $tableB)
        );
    }

    public function testCompareForeignKeyBasedOnPropertiesNotName()
    {
        $tableA = new Table('foo');
        $tableA->addColumn('id', 'integer');
        $tableA->addNamedForeignKeyConstraint('foo_constraint', 'bar', ['id'], ['id']);

        $tableB = new Table('foo');
        $tableB->addColumn('ID', 'integer');
        $tableB->addNamedForeignKeyConstraint('bar_constraint', 'bar', ['id'], ['id']);

        $c         = new Comparator();
        $tableDiff = $c->diffTable($tableA, $tableB);

        self::assertFalse($tableDiff);
    }

    public function testCompareForeignKeyRestrictNoActionAreTheSame()
    {
        $fk1 = new ForeignKeyConstraint(['foo'], 'bar', ['baz'], 'fk1', ['onDelete' => 'NO ACTION']);
        $fk2 = new ForeignKeyConstraint(['foo'], 'bar', ['baz'], 'fk1', ['onDelete' => 'RESTRICT']);

        $c = new Comparator();
        self::assertFalse($c->diffForeignKey($fk1, $fk2));
    }

    /**
     * @group DBAL-492
     */
    public function testCompareForeignKeyNamesUnqualifiedAsNoSchemaInformationIsAvailable()
    {
        $fk1 = new ForeignKeyConstraint(['foo'], 'foo.bar', ['baz'], 'fk1');
        $fk2 = new ForeignKeyConstraint(['foo'], 'baz.bar', ['baz'], 'fk1');

        $c = new Comparator();
        self::assertFalse($c->diffForeignKey($fk1, $fk2));
    }

    public function testDetectRenameColumn()
    {
        $tableA = new Table('foo');
        $tableA->addColumn('foo', 'integer');

        $tableB = new Table('foo');
        $tableB->addColumn('bar', 'integer');

        $c         = new Comparator();
        $tableDiff = $c->diffTable($tableA, $tableB);

        self::assertCount(0, $tableDiff->addedColumns);
        self::assertCount(0, $tableDiff->removedColumns);
        self::assertArrayHasKey('foo', $tableDiff->renamedColumns);
        self::assertEquals('bar', $tableDiff->renamedColumns['foo']->getName());
    }

    /**
     * You can easily have ambiguities in the column renaming. If these
     * are detected no renaming should take place, instead adding and dropping
     * should be used exclusively.
     *
     * @group DBAL-24
     */
    public function testDetectRenameColumnAmbiguous()
    {
        $tableA = new Table('foo');
        $tableA->addColumn('foo', 'integer');
        $tableA->addColumn('bar', 'integer');

        $tableB = new Table('foo');
        $tableB->addColumn('baz', 'integer');

        $c         = new Comparator();
        $tableDiff = $c->diffTable($tableA, $tableB);

        self::assertCount(1, $tableDiff->addedColumns, "'baz' should be added, not created through renaming!");
        self::assertArrayHasKey('baz', $tableDiff->addedColumns, "'baz' should be added, not created through renaming!");
        self::assertCount(2, $tableDiff->removedColumns, "'foo' and 'bar' should both be dropped, an ambiguity exists which one could be renamed to 'baz'.");
        self::assertArrayHasKey('foo', $tableDiff->removedColumns, "'foo' should be removed.");
        self::assertArrayHasKey('bar', $tableDiff->removedColumns, "'bar' should be removed.");
        self::assertCount(0, $tableDiff->renamedColumns, 'no renamings should take place.');
    }

    /**
     * @group DBAL-1063
     */
    public function testDetectRenameIndex()
    {
        $table1 = new Table('foo');
        $table1->addColumn('foo', 'integer');

        $table2 = clone $table1;

        $table1->addIndex(['foo'], 'idx_foo');

        $table2->addIndex(['foo'], 'idx_bar');

        $comparator = new Comparator();
        $tableDiff  = $comparator->diffTable($table1, $table2);

        self::assertCount(0, $tableDiff->addedIndexes);
        self::assertCount(0, $tableDiff->removedIndexes);
        self::assertArrayHasKey('idx_foo', $tableDiff->renamedIndexes);
        self::assertEquals('idx_bar', $tableDiff->renamedIndexes['idx_foo']->getName());
    }

    /**
     * You can easily have ambiguities in the index renaming. If these
     * are detected no renaming should take place, instead adding and dropping
     * should be used exclusively.
     *
     * @group DBAL-1063
     */
    public function testDetectRenameIndexAmbiguous()
    {
        $table1 = new Table('foo');
        $table1->addColumn('foo', 'integer');

        $table2 = clone $table1;

        $table1->addIndex(['foo'], 'idx_foo');
        $table1->addIndex(['foo'], 'idx_bar');

        $table2->addIndex(['foo'], 'idx_baz');

        $comparator = new Comparator();
        $tableDiff  = $comparator->diffTable($table1, $table2);

        self::assertCount(1, $tableDiff->addedIndexes);
        self::assertArrayHasKey('idx_baz', $tableDiff->addedIndexes);
        self::assertCount(2, $tableDiff->removedIndexes);
        self::assertArrayHasKey('idx_foo', $tableDiff->removedIndexes);
        self::assertArrayHasKey('idx_bar', $tableDiff->removedIndexes);
        self::assertCount(0, $tableDiff->renamedIndexes);
    }

    public function testDetectChangeIdentifierType()
    {
        $this->markTestSkipped('DBAL-2 was reopened, this test cannot work anymore.');

        $tableA = new Table('foo');
        $tableA->addColumn('id', 'integer', ['autoincrement' => false]);

        $tableB = new Table('foo');
        $tableB->addColumn('id', 'integer', ['autoincrement' => true]);

        $c         = new Comparator();
        $tableDiff = $c->diffTable($tableA, $tableB);

        self::assertInstanceOf(TableDiff::class, $tableDiff);
        self::assertArrayHasKey('id', $tableDiff->changedColumns);
    }


    /**
     * @group DBAL-105
     */
    public function testDiff()
    {
        $table = new Table('twitter_users');
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
        $table->addColumn('twitterId', 'integer');
        $table->addColumn('displayName', 'string');
        $table->setPrimaryKey(['id']);

        $newtable = new Table('twitter_users');
        $newtable->addColumn('id', 'integer', ['autoincrement' => true]);
        $newtable->addColumn('twitter_id', 'integer');
        $newtable->addColumn('display_name', 'string');
        $newtable->addColumn('logged_in_at', 'datetime');
        $newtable->setPrimaryKey(['id']);

        $c         = new Comparator();
        $tableDiff = $c->diffTable($table, $newtable);

        self::assertInstanceOf(TableDiff::class, $tableDiff);
        self::assertEquals(['twitterid', 'displayname'], array_keys($tableDiff->renamedColumns));
        self::assertEquals(['logged_in_at'], array_keys($tableDiff->addedColumns));
        self::assertCount(0, $tableDiff->removedColumns);
    }


    /**
     * @group DBAL-112
     */
    public function testChangedSequence()
    {
        $schema   = new Schema();
        $sequence = $schema->createSequence('baz');

        $schemaNew = clone $schema;
        $schemaNew->getSequence('baz')->setAllocationSize(20);

        $c    = new Comparator();
        $diff = $c->compare($schema, $schemaNew);

        self::assertSame($diff->changedSequences[0], $schemaNew->getSequence('baz'));
    }

    /**
     * @group DBAL-106
     */
    public function testDiffDecimalWithNullPrecision()
    {
        $column = new Column('foo', Type::getType('decimal'));
        $column->setPrecision(null);

        $column2 = new Column('foo', Type::getType('decimal'));

        $c = new Comparator();
        self::assertEquals([], $c->diffColumn($column, $column2));
    }

    /**
     * @group DBAL-204
     */
    public function testFqnSchemaComparison()
    {
        $config = new SchemaConfig();
        $config->setName('foo');

        $oldSchema = new Schema([], [], $config);
        $oldSchema->createTable('bar');

        $newSchema = new Schema([], [], $config);
        $newSchema->createTable('foo.bar');

        $expected             = new SchemaDiff();
        $expected->fromSchema = $oldSchema;

        self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema));
    }

    /**
     * @group DBAL-669
     */
    public function testNamespacesComparison()
    {
        $config = new SchemaConfig();
        $config->setName('schemaName');

        $oldSchema = new Schema([], [], $config);
        $oldSchema->createTable('taz');
        $oldSchema->createTable('war.tab');

        $newSchema = new Schema([], [], $config);
        $newSchema->createTable('bar.tab');
        $newSchema->createTable('baz.tab');
        $newSchema->createTable('war.tab');

        $expected                = new SchemaDiff();
        $expected->fromSchema    = $oldSchema;
        $expected->newNamespaces = ['bar' => 'bar', 'baz' => 'baz'];

        $diff = Comparator::compareSchemas($oldSchema, $newSchema);

        self::assertEquals(['bar' => 'bar', 'baz' => 'baz'], $diff->newNamespaces);
        self::assertCount(2, $diff->newTables);
    }

    /**
     * @group DBAL-204
     */
    public function testFqnSchemaComparisonDifferentSchemaNameButSameTableNoDiff()
    {
        $config = new SchemaConfig();
        $config->setName('foo');

        $oldSchema = new Schema([], [], $config);
        $oldSchema->createTable('foo.bar');

        $newSchema = new Schema();
        $newSchema->createTable('bar');

        $expected             = new SchemaDiff();
        $expected->fromSchema = $oldSchema;

        self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema));
    }

    /**
     * @group DBAL-204
     */
    public function testFqnSchemaComparisonNoSchemaSame()
    {
        $config = new SchemaConfig();
        $config->setName('foo');
        $oldSchema = new Schema([], [], $config);
        $oldSchema->createTable('bar');

        $newSchema = new Schema();
        $newSchema->createTable('bar');

        $expected             = new SchemaDiff();
        $expected->fromSchema = $oldSchema;

        self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema));
    }

    /**
     * @group DDC-1657
     */
    public function testAutoIncrementSequences()
    {
        $oldSchema = new Schema();
        $table     = $oldSchema->createTable('foo');
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
        $table->setPrimaryKey(['id']);
        $oldSchema->createSequence('foo_id_seq');

        $newSchema = new Schema();
        $table     = $newSchema->createTable('foo');
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
        $table->setPrimaryKey(['id']);

        $c    = new Comparator();
        $diff = $c->compare($oldSchema, $newSchema);

        self::assertCount(0, $diff->removedSequences);
    }


    /**
     * Check that added autoincrement sequence is not populated in newSequences
     *
     * @group DBAL-562
     */
    public function testAutoIncrementNoSequences()
    {
        $oldSchema = new Schema();
        $table     = $oldSchema->createTable('foo');
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
        $table->setPrimaryKey(['id']);

        $newSchema = new Schema();
        $table     = $newSchema->createTable('foo');
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
        $table->setPrimaryKey(['id']);
        $newSchema->createSequence('foo_id_seq');

        $c    = new Comparator();
        $diff = $c->compare($oldSchema, $newSchema);

        self::assertCount(0, $diff->newSequences);
    }
    /**
     * You can get multiple drops for a FK when a table referenced by a foreign
     * key is deleted, as this FK is referenced twice, once on the orphanedForeignKeys
     * array because of the dropped table, and once on changedTables array. We
     * now check that the key is present once.
     */
    public function testAvoidMultipleDropForeignKey()
    {
        $oldSchema = new Schema();

        $tableA = $oldSchema->createTable('table_a');
        $tableA->addColumn('id', 'integer');

        $tableB = $oldSchema->createTable('table_b');
        $tableB->addColumn('id', 'integer');

        $tableC = $oldSchema->createTable('table_c');
        $tableC->addColumn('id', 'integer');
        $tableC->addColumn('table_a_id', 'integer');
        $tableC->addColumn('table_b_id', 'integer');

        $tableC->addForeignKeyConstraint($tableA, ['table_a_id'], ['id']);
        $tableC->addForeignKeyConstraint($tableB, ['table_b_id'], ['id']);

        $newSchema = new Schema();

        $tableB = $newSchema->createTable('table_b');
        $tableB->addColumn('id', 'integer');

        $tableC = $newSchema->createTable('table_c');
        $tableC->addColumn('id', 'integer');

        $comparator = new Comparator();
        $schemaDiff = $comparator->compare($oldSchema, $newSchema);

        self::assertCount(1, $schemaDiff->changedTables['table_c']->removedForeignKeys);
        self::assertCount(1, $schemaDiff->orphanedForeignKeys);
    }

    public function testCompareChangedColumn()
    {
        $oldSchema = new Schema();

        $tableFoo = $oldSchema->createTable('foo');
        $tableFoo->addColumn('id', 'integer');

        $newSchema = new Schema();
        $table     = $newSchema->createTable('foo');
        $table->addColumn('id', 'string');

        $expected                      = new SchemaDiff();
        $expected->fromSchema          = $oldSchema;
        $tableDiff                     = $expected->changedTables['foo'] = new TableDiff('foo');
        $tableDiff->fromTable          = $tableFoo;
        $columnDiff                    = $tableDiff->changedColumns['id'] = new ColumnDiff('id', $table->getColumn('id'));
        $columnDiff->fromColumn        = $tableFoo->getColumn('id');
        $columnDiff->changedProperties = ['type'];

        self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema));
    }

    public function testCompareChangedBinaryColumn()
    {
        $oldSchema = new Schema();

        $tableFoo = $oldSchema->createTable('foo');
        $tableFoo->addColumn('id', 'binary');

        $newSchema = new Schema();
        $table     = $newSchema->createTable('foo');
        $table->addColumn('id', 'binary', ['length' => 42, 'fixed' => true]);

        $expected                      = new SchemaDiff();
        $expected->fromSchema          = $oldSchema;
        $tableDiff                     = $expected->changedTables['foo'] = new TableDiff('foo');
        $tableDiff->fromTable          = $tableFoo;
        $columnDiff                    = $tableDiff->changedColumns['id'] = new ColumnDiff('id', $table->getColumn('id'));
        $columnDiff->fromColumn        = $tableFoo->getColumn('id');
        $columnDiff->changedProperties = ['length', 'fixed'];

        self::assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema));
    }

    /**
     * @group DBAL-617
     */
    public function testCompareQuotedAndUnquotedForeignKeyColumns()
    {
        $fk1 = new ForeignKeyConstraint(['foo'], 'bar', ['baz'], 'fk1', ['onDelete' => 'NO ACTION']);
        $fk2 = new ForeignKeyConstraint(['`foo`'], 'bar', ['`baz`'], 'fk1', ['onDelete' => 'NO ACTION']);

        $comparator = new Comparator();
        $diff       = $comparator->diffForeignKey($fk1, $fk2);

        self::assertFalse($diff);
    }

    /**
     * @param SchemaDiff $diff
     * @param int        $newTableCount
     * @param int        $changeTableCount
     * @param int        $removeTableCount
     */
    public function assertSchemaTableChangeCount($diff, $newTableCount = 0, $changeTableCount = 0, $removeTableCount = 0)
    {
        self::assertCount($newTableCount, $diff->newTables);
        self::assertCount($changeTableCount, $diff->changedTables);
        self::assertCount($removeTableCount, $diff->removedTables);
    }

    /**
     * @param SchemaDiff $diff
     * @param int        $newSequenceCount
     * @param int        $changeSequenceCount
     * @param int        $changeSequenceCount
     */
    public function assertSchemaSequenceChangeCount($diff, $newSequenceCount = 0, $changeSequenceCount = 0, $removeSequenceCount = 0)
    {
        self::assertCount($newSequenceCount, $diff->newSequences, 'Expected number of new sequences is wrong.');
        self::assertCount($changeSequenceCount, $diff->changedSequences, 'Expected number of changed sequences is wrong.');
        self::assertCount($removeSequenceCount, $diff->removedSequences, 'Expected number of removed sequences is wrong.');
    }

    public function testDiffColumnPlatformOptions()
    {
        $column1 = new Column('foo', Type::getType('string'), ['platformOptions' => ['foo' => 'foo', 'bar' => 'bar']]);
        $column2 = new Column('foo', Type::getType('string'), ['platformOptions' => ['foo' => 'foo', 'foobar' => 'foobar']]);
        $column3 = new Column('foo', Type::getType('string'), ['platformOptions' => ['foo' => 'foo', 'bar' => 'rab']]);
        $column4 = new Column('foo', Type::getType('string'));

        $comparator = new Comparator();

        self::assertEquals([], $comparator->diffColumn($column1, $column2));
        self::assertEquals([], $comparator->diffColumn($column2, $column1));
        self::assertEquals(['bar'], $comparator->diffColumn($column1, $column3));
        self::assertEquals(['bar'], $comparator->diffColumn($column3, $column1));
        self::assertEquals([], $comparator->diffColumn($column1, $column4));
        self::assertEquals([], $comparator->diffColumn($column4, $column1));
    }

    public function testComplexDiffColumn()
    {
        $column1 = new Column('foo', Type::getType('string'), [
            'platformOptions' => ['foo' => 'foo'],
            'customSchemaOptions' => ['foo' => 'bar'],
        ]);

        $column2 = new Column('foo', Type::getType('string'), [
            'platformOptions' => ['foo' => 'bar'],
        ]);

        $comparator = new Comparator();

        self::assertEquals([], $comparator->diffColumn($column1, $column2));
        self::assertEquals([], $comparator->diffColumn($column2, $column1));
    }

    /**
     * @group DBAL-669
     */
    public function testComparesNamespaces()
    {
        $comparator = new Comparator();
        $fromSchema = $this->getMockBuilder(Schema::class)
            ->setMethods(['getNamespaces', 'hasNamespace'])
            ->getMock();
        $toSchema   = $this->getMockBuilder(Schema::class)
            ->setMethods(['getNamespaces', 'hasNamespace'])
            ->getMock();

        $fromSchema->expects($this->once())
            ->method('getNamespaces')
            ->will($this->returnValue(['foo', 'bar']));

        $fromSchema->expects($this->at(0))
            ->method('hasNamespace')
            ->with('bar')
            ->will($this->returnValue(true));

        $fromSchema->expects($this->at(1))
            ->method('hasNamespace')
            ->with('baz')
            ->will($this->returnValue(false));

        $toSchema->expects($this->once())
            ->method('getNamespaces')
            ->will($this->returnValue(['bar', 'baz']));

        $toSchema->expects($this->at(1))
            ->method('hasNamespace')
            ->with('foo')
            ->will($this->returnValue(false));

        $toSchema->expects($this->at(2))
            ->method('hasNamespace')
            ->with('bar')
            ->will($this->returnValue(true));

        $expected                    = new SchemaDiff();
        $expected->fromSchema        = $fromSchema;
        $expected->newNamespaces     = ['baz' => 'baz'];
        $expected->removedNamespaces = ['foo' => 'foo'];

        self::assertEquals($expected, $comparator->compare($fromSchema, $toSchema));
    }

    public function testCompareGuidColumns()
    {
        $comparator = new Comparator();

        $column1 = new Column('foo', Type::getType('guid'), ['comment' => 'GUID 1']);
        $column2 = new Column(
            'foo',
            Type::getType('guid'),
            ['notnull' => false, 'length' => '36', 'fixed' => true, 'default' => 'NEWID()', 'comment' => 'GUID 2.']
        );

        self::assertEquals(['notnull', 'default', 'comment'], $comparator->diffColumn($column1, $column2));
        self::assertEquals(['notnull', 'default', 'comment'], $comparator->diffColumn($column2, $column1));
    }

    /**
     * @group DBAL-1009
     * @dataProvider getCompareColumnComments
     */
    public function testCompareColumnComments($comment1, $comment2, $equals)
    {
        $column1 = new Column('foo', Type::getType('integer'), ['comment' => $comment1]);
        $column2 = new Column('foo', Type::getType('integer'), ['comment' => $comment2]);

        $comparator = new Comparator();

        $expectedDiff = $equals ? [] : ['comment'];

        $actualDiff = $comparator->diffColumn($column1, $column2);

        self::assertSame($expectedDiff, $actualDiff);

        $actualDiff = $comparator->diffColumn($column2, $column1);

        self::assertSame($expectedDiff, $actualDiff);
    }

    public function getCompareColumnComments()
    {
        return [
            [null, null, true],
            ['', '', true],
            [' ', ' ', true],
            ['0', '0', true],
            ['foo', 'foo', true],

            [null, '', true],
            [null, ' ', false],
            [null, '0', false],
            [null, 'foo', false],

            ['', ' ', false],
            ['', '0', false],
            ['', 'foo', false],

            [' ', '0', false],
            [' ', 'foo', false],

            ['0', 'foo', false],
        ];
    }

    public function testForeignKeyRemovalWithRenamedLocalColumn()
    {
        $fromSchema = new Schema([
            'table1' => new Table(
                'table1',
                [
                    'id' => new Column('id', Type::getType('integer')),
                ]
            ),
            'table2' => new Table(
                'table2',
                [
                    'id' => new Column('id', Type::getType('integer')),
                    'id_table1' => new Column('id_table1', Type::getType('integer')),
                ],
                [],
                [
                    new ForeignKeyConstraint(['id_table1'], 'table1', ['id'], 'fk_table2_table1'),
                ]
            ),
        ]);
        $toSchema   = new Schema([
            'table2' => new Table(
                'table2',
                [
                    'id' => new Column('id', Type::getType('integer')),
                    'id_table3' => new Column('id_table3', Type::getType('integer')),
                ],
                [],
                [
                    new ForeignKeyConstraint(['id_table3'], 'table3', ['id'], 'fk_table2_table3'),
                ]
            ),
            'table3' => new Table(
                'table3',
                [
                    'id' => new Column('id', Type::getType('integer')),
                ]
            ),
        ]);
        $actual     = Comparator::compareSchemas($fromSchema, $toSchema);
        self::assertArrayHasKey('table2', $actual->changedTables);
        self::assertCount(1, $actual->orphanedForeignKeys);
        self::assertEquals('fk_table2_table1', $actual->orphanedForeignKeys[0]->getName());
        self::assertCount(1, $actual->changedTables['table2']->addedForeignKeys, 'FK to table3 should be added.');
        self::assertEquals('table3', $actual->changedTables['table2']->addedForeignKeys[0]->getForeignTableName());
    }
}