From f22a56cdd7451f6c245ef03ed8e6e9a3f70fe81f Mon Sep 17 00:00:00 2001 From: beberlei Date: Sat, 28 Nov 2009 14:30:08 +0000 Subject: [PATCH] [2.0] - DDC-169 - Fixed another regression in refactored SchemaTool, began porting of eZ Components Database Schema Comparator, SchemaDiff and TableDiff. --- lib/Doctrine/DBAL/Schema/Comparator.php | 219 +++++++++++ lib/Doctrine/DBAL/Schema/Index.php | 15 +- lib/Doctrine/DBAL/Schema/SchemaDiff.php | 71 ++++ lib/Doctrine/DBAL/Schema/TableDiff.php | 100 +++++ lib/Doctrine/ORM/Tools/SchemaTool.php | 2 +- tests/Doctrine/Tests/DBAL/AllTests.php | 1 + .../Tests/DBAL/Schema/ComparatorTest.php | 357 ++++++++++++++++++ 7 files changed, 759 insertions(+), 6 deletions(-) create mode 100644 lib/Doctrine/DBAL/Schema/Comparator.php create mode 100644 lib/Doctrine/DBAL/Schema/SchemaDiff.php create mode 100644 lib/Doctrine/DBAL/Schema/TableDiff.php create mode 100644 tests/Doctrine/Tests/DBAL/Schema/ComparatorTest.php diff --git a/lib/Doctrine/DBAL/Schema/Comparator.php b/lib/Doctrine/DBAL/Schema/Comparator.php new file mode 100644 index 000000000..a6805d604 --- /dev/null +++ b/lib/Doctrine/DBAL/Schema/Comparator.php @@ -0,0 +1,219 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * Compare to Schemas and return an instance of SchemaDiff + * + * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 2.0 + * @version $Revision$ + * @author Benjamin Eberlei + */ +class Comparator +{ + /** + * @param Schema $fromSchema + * @param Schema $toSchema + * @return SchemaDiff + */ + static public function compareSchemas( Schema $fromSchema, Schema $toSchema ) + { + $c = new self(); + return $c->compare($fromSchema, $toSchema); + } + + /** + * Returns a SchemaDiff object containing the differences between the schemas $fromSchema and $toSchema. + * + * The returned diferences are returned in such a way that they contain the + * operations to change the schema stored in $fromSchema to the schema that is + * stored in $toSchema. + * + * @param Schema $fromSchema + * @param Schema $toSchema + * + * @return SchemaDiff + */ + public function compare( Schema $fromSchema, Schema $toSchema ) + { + $diff = new SchemaDiff(); + + foreach ( $toSchema->getTables() as $tableName => $table ) { + if ( !$fromSchema->hasTable($tableName) ) { + $diff->newTables[$tableName] = $table; + } else { + $tableDifferences = $this->diffTable( $fromSchema->getTable($tableName), $table ); + if ( $tableDifferences !== false ) { + $diff->changedTables[$tableName] = $tableDifferences; + } + } + } + + /* Check if there are tables removed */ + foreach ( $fromSchema->getTables() as $tableName => $table ) { + if ( !$toSchema->hasTable($tableName) ) { + $diff->removedTables[$tableName] = $table; + } + } + + // Todo add code for sequence diff + + return $diff; + } + + /** + * Returns the difference between the tables $table1 and $table2. + * + * If there are no differences this method returns the boolean false. + * + * @param Table $table1 + * @param Table $table2 + * + * @return bool|TableDiff + */ + private function diffTable( Table $table1, Table $table2 ) + { + $changes = 0; + $tableDifferences = new TableDiff(); + + /* See if all the fields in table 1 exist in table 2 */ + foreach ( $table2->getColumns() as $columnName => $column ) { + if ( !$table1->hasColumn($columnName) ) { + $tableDifferences->addedFields[$columnName] = $column; + $changes++; + } + } + /* See if there are any removed fields in table 2 */ + foreach ( $table1->getColumns() as $columnName => $column ) { + if ( !$table2->hasColumn($columnName) ) { + $tableDifferences->removedFields[$columnName] = true; + $changes++; + } + } + /* See if there are any changed fieldDefinitioninitions */ + foreach ( $table1->getColumns() as $columnName => $column ) { + if ( $table2->hasColumn($columnName) ) { + $fieldDifferences = $this->diffField( $column, $table2->getColumn($columnName) ); + if ( $fieldDifferences ) { + $tableDifferences->changedFields[$columnName] = $fieldDifferences; + $changes++; + } + } + } + + $table1Indexes = $table1->getIndexes(); + $table2Indexes = $table2->getIndexes(); + + /* See if all the indexes in table 1 exist in table 2 */ + foreach ( $table2Indexes as $indexName => $indexDefinition ) { + if ( !isset( $table1Indexes[$indexName] ) ) { + $tableDifferences->addedIndexes[$indexName] = $indexDefinition; + $changes++; + } + } + /* See if there are any removed indexes in table 2 */ + foreach ( $table1Indexes as $indexName => $indexDefinition ) { + if ( !isset( $table2Indexes[$indexName] ) ) { + $tableDifferences->removedIndexes[$indexName] = true; + $changes++; + } + } + /* See if there are any changed indexDefinitions */ + foreach ( $table1Indexes as $indexName => $indexDefinition ) { + if ( isset( $table2Indexes[$indexName] ) ) { + $indexDifferences = $this->diffIndex( $indexDefinition, $table2Indexes[$indexName] ); + if ( $indexDifferences ) { + $tableDifferences->changedIndexes[$indexName] = $indexDifferences; + $changes++; + } + } + } + + // Todo add code for foreign key differences + + return $changes ? $tableDifferences : false; + } + + /** + * Returns the difference between the fields $field1 and $field2. + * + * If there are differences this method returns $field2, otherwise the + * boolean false. + * + * @param Column $column1 + * @param Column $column2 + * + * @return bool|Column + */ + private function diffField( Column $column1, Column $column2 ) + { + if ( $column1->getType() != $column2->getType() ) { + return $column2; + } + + return false; + } + + /** + * Finds the difference between the indexes $index1 and $index2. + * + * Compares $index1 with $index2 and returns $index2 if there are any + * differences or false in case there are no differences. + * + * @param Index $index1 + * @param Index $index2 + * @return bool|Index + */ + private function diffIndex( Index $index1, Index $index2 ) + { + if($index1->isPrimary() != $index2->isPrimary()) { + return $index2; + } + if($index1->isUnique() != $index2->isUnique()) { + return $index2; + } + + // Check for removed index fields in $index2 + $index1Columns = $index1->getColumns(); + for($i = 0; $i < count($index1Columns); $i++) { + $indexColumn = $index1Columns[$i]; + if (!$index2->hasColumnAtPosition($indexColumn, $i)) { + return $index2; + } + } + + // Check for new index fields in $index2 + $index2Columns = $index2->getColumns(); + for($i = 0; $i < count($index2Columns); $i++) { + $indexColumn = $index2Columns[$i]; + if (!$index1->hasColumnAtPosition($indexColumn, $i)) { + return $index2; + } + } + + return false; + } +} \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Schema/Index.php b/lib/Doctrine/DBAL/Schema/Index.php index 022896e96..3b6783a35 100644 --- a/lib/Doctrine/DBAL/Schema/Index.php +++ b/lib/Doctrine/DBAL/Schema/Index.php @@ -25,11 +25,6 @@ use Doctrine\DBAL\Schema\Visitor\Visitor; class Index extends AbstractAsset { - /** - * @var string - */ - protected $_indexName; - /** * @var array */ @@ -97,4 +92,14 @@ class Index extends AbstractAsset { return $this->_isPrimary; } + + /** + * @param string $columnName + * @param int $pos + * @return bool + */ + public function hasColumnAtPosition($columnName, $pos=0) + { + return \array_search($columnName, $this->_columns) === $pos; + } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Schema/SchemaDiff.php b/lib/Doctrine/DBAL/Schema/SchemaDiff.php new file mode 100644 index 000000000..3bea9ef28 --- /dev/null +++ b/lib/Doctrine/DBAL/Schema/SchemaDiff.php @@ -0,0 +1,71 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * Schema Diff + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @since 2.0 + * @version $Revision$ + * @author Benjamin Eberlei + */ +class SchemaDiff +{ + /** + * All added tables + * + * @var array(string=>ezcDbSchemaTable) + */ + public $newTables; + + /** + * All changed tables + * + * @var array(string=>ezcDbSchemaTableDiff) + */ + public $changedTables; + + /** + * All removed tables + * + * @var array(string=>bool) + */ + public $removedTables; + + /** + * Constructs an SchemaDiff object. + * + * @param array(string=>Table) $newTables + * @param array(string=>TableDiff) $changedTables + * @param array(string=>bool) $removedTables + */ + public function __construct( $newTables = array(), $changedTables = array(), $removedTables = array() ) + { + $this->newTables = $newTables; + $this->changedTables = $changedTables; + $this->removedTables = $removedTables; + } +} diff --git a/lib/Doctrine/DBAL/Schema/TableDiff.php b/lib/Doctrine/DBAL/Schema/TableDiff.php new file mode 100644 index 000000000..322d5622a --- /dev/null +++ b/lib/Doctrine/DBAL/Schema/TableDiff.php @@ -0,0 +1,100 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * Table Diff + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @since 2.0 + * @version $Revision$ + * @author Benjamin Eberlei + */ +class TableDiff +{ + /** + * All added fields + * + * @var array(string=>ezcDbSchemaField) + */ + public $addedFields; + + /** + * All changed fields + * + * @var array(string=>ezcDbSchemaField) + */ + public $changedFields; + + /** + * All removed fields + * + * @var array(string=>bool) + */ + public $removedFields; + + /** + * All added indexes + * + * @var array(string=>ezcDbSchemaIndex) + */ + public $addedIndexes; + + /** + * All changed indexes + * + * @var array(string=>ezcDbSchemaIndex) + */ + public $changedIndexes; + + /** + * All removed indexes + * + * @var array(string=>bool) + */ + public $removedIndexes; + + /** + * Constructs an TableDiff object. + * + * @param array(string=>Column) $addedFields + * @param array(string=>Column) $changedFields + * @param array(string=>bool) $removedFields + * @param array(string=>Index) $addedIndexes + * @param array(string=>Index) $changedIndexes + * @param array(string=>bool) $removedIndexes + */ + function __construct( $addedFields = array(), $changedFields = array(), + $removedFields = array(), $addedIndexes = array(), $changedIndexes = + array(), $removedIndexes = array() ) + { + $this->addedFields = $addedFields; + $this->changedFields = $changedFields; + $this->removedFields = $removedFields; + $this->addedIndexes = $addedIndexes; + $this->changedIndexes = $changedIndexes; + $this->removedIndexes = $removedIndexes; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index c03e08a2b..5c2a35163 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -128,7 +128,7 @@ class SchemaTool if ($class->isInheritanceTypeSingleTable()) { $columns = $this->_gatherColumns($class, $table); - $this->_gatherRelationsSql($class, $sql, $columns, $table, $schema); + $this->_gatherRelationsSql($class, $table, $schema); // Add the discriminator column $discrColumnDef = $this->_getDiscriminatorColumnDefinition($class, $table); diff --git a/tests/Doctrine/Tests/DBAL/AllTests.php b/tests/Doctrine/Tests/DBAL/AllTests.php index 9448d9f14..a7de1bfd7 100644 --- a/tests/Doctrine/Tests/DBAL/AllTests.php +++ b/tests/Doctrine/Tests/DBAL/AllTests.php @@ -48,6 +48,7 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\DBAL\Schema\TableTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Schema\SchemaTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Schema\Visitor\SchemaSqlCollectorTest'); + $suite->addTestSuite('Doctrine\Tests\DBAL\Schema\ComparatorTest'); // Driver manager test $suite->addTestSuite('Doctrine\Tests\DBAL\DriverManagerTest'); diff --git a/tests/Doctrine/Tests/DBAL/Schema/ComparatorTest.php b/tests/Doctrine/Tests/DBAL/Schema/ComparatorTest.php new file mode 100644 index 000000000..0cb1c5f23 --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Schema/ComparatorTest.php @@ -0,0 +1,357 @@ +. + */ + +namespace Doctrine\Tests\DBAL\Schema; + +require_once __DIR__ . '/../../TestInit.php'; + +use Doctrine\DBAL\Schema\Schema, + Doctrine\DBAL\Schema\Table, + Doctrine\DBAL\Schema\Column, + Doctrine\DBAL\Schema\Index, + Doctrine\DBAL\Schema\Sequence, + Doctrine\DBAL\Schema\SchemaDiff, + Doctrine\DBAL\Schema\TableDiff, + Doctrine\DBAL\Schema\Comparator, + Doctrine\DBAL\Types\Type; + +/** + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @since 2.0 + * @version $Revision$ + * @author Benjamin Eberlei + */ +class ComparatorTest extends \PHPUnit_Framework_TestCase +{ + public function testCompareSame1() + { + $schema1 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer' ) ), + ) + ), + ) ); + $schema2 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer') ), + ) + ), + ) ); + + $this->assertEquals(new SchemaDiff(), Comparator::compareSchemas( $schema1, $schema2 ) ); + } + + public function testCompareSame2() + { + $schema1 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ) + ), + ) ); + $schema2 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + ) + ), + ) ); + $this->assertEquals(new SchemaDiff(), Comparator::compareSchemas( $schema1, $schema2 ) ); + } + + public function testCompareMissingTable() + { + $schema1 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + ) + ), + ) ); + $schema2 = new Schema( array( + ) ); + + $expected = new SchemaDiff( array(), array(), + array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + ) + ), + ) + ); + $this->assertEquals($expected, Comparator::compareSchemas( $schema1, $schema2 ) ); + } + + public function testCompareNewTable() + { + $schema1 = new Schema( array( + ) ); + $schema2 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + ) + ), + ) ); + + $expected = new SchemaDiff( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + ) + ), + ) ); + $this->assertEquals($expected, Comparator::compareSchemas( $schema1, $schema2 ) ); + } + + public function testCompareMissingField() + { + $schema1 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ) + ), + ) ); + $schema2 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ) + ), + ) ); + + $expected = new SchemaDiff ( array(), + array ( + 'bugdb' => new TableDiff( array(), array(), + array ( + 'integerfield1' => true, + ) + ) + ) + ); + $this->assertEquals($expected, Comparator::compareSchemas( $schema1, $schema2 ) ); + } + + public function testCompareNewField() + { + $schema1 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + ) + ), + ) ); + $schema2 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ) + ), + ) ); + + $expected = new SchemaDiff ( array(), + array ( + 'bugdb' => new TableDiff ( + array ( + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ) + ), + ) + ); + $this->assertEquals($expected, Comparator::compareSchemas( $schema1, $schema2 ) ); + } + + public function testCompareChangedColumns_ChangeType() + { + $schema1 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'charfield1' => new Column('charfield1', Type::getType('string')), + ) + ), + ) ); + $schema2 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'charfield1' => new Column('charfield1', Type::getType('integer')), + ) + ), + ) ); + + $expected = new SchemaDiff ( array(), + array ( + 'bugdb' => new TableDiff( array(), + array ( + 'charfield1' => new Column('charfield1', Type::getType('integer')), + ) + ), + ) + ); + $this->assertEquals($expected, Comparator::compareSchemas( $schema1, $schema2 ) ); + } + + public function testCompareRemovedIndex() + { + $schema1 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ), + array ( + 'primary' => new Index('primary', + array( + 'integerfield1' + ), + true + ) + ) + ), + ) ); + $schema2 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ) + ), + ) ); + + $expected = new SchemaDiff ( array(), + array ( + 'bugdb' => new TableDiff( array(), array(), array(), array(), array(), + array ( + 'primary' => true + ) + ), + ) + ); + $this->assertEquals($expected, Comparator::compareSchemas( $schema1, $schema2 ) ); + } + + public function testCompareNewIndex() + { + $schema1 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ) + ), + ) ); + $schema2 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ), + array ( + 'primary' => new Index('primary', + array( + 'integerfield1' + ), + true + ) + ) + ), + ) ); + + $expected = new SchemaDiff ( array(), + array ( + 'bugdb' => new TableDiff( array(), array(), array(), + array ( + 'primary' => new Index('primary', + array( + 'integerfield1' + ), + true + ) + ) + ), + ) + ); + $this->assertEquals($expected, Comparator::compareSchemas( $schema1, $schema2 ) ); + } + + public function testCompareChangedIndex() + { + $schema1 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ), + array ( + 'primary' => new Index('primary', + array( + 'integerfield1' + ), + true + ) + ) + ), + ) ); + $schema2 = new Schema( array( + 'bugdb' => new Table('bugdb', + array ( + 'integerfield1' => new Column('integerfield1', Type::getType('integer')), + 'integerfield2' => new Column('integerfield2', Type::getType('integer')), + ), + array ( + 'primary' => new Index('primary', + array('integerfield1', 'integerfield2'), + true + ) + ) + ), + ) ); + + $expected = new SchemaDiff ( array(), + array ( + 'bugdb' => new TableDiff( array(), array(), array(), array(), + array ( + 'primary' => new Index('primary', + array( + 'integerfield1', + 'integerfield2' + ), + true + ) + ) + ), + ) + ); + $actual = Comparator::compareSchemas( $schema1, $schema2 ); + $this->assertEquals($expected, $actual); + } +} \ No newline at end of file