Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Platform-aware schema comparison #4746

Merged
merged 3 commits into from Aug 26, 2021

Conversation

morozov
Copy link
Member

@morozov morozov commented Aug 15, 2021

State

The existing property-based column definition comparison has quite some flaws (see all the associated issues):

  1. Certain properties, even if different, may yield the same column definition (e.g. the length of a blob column within the type limits).
  2. The above is platform-specific, so if a certain type's property is ignored on one platform, it can cause a false-positive match on another platform.

Proposal

The idea is to replace property-based comparison with the comparison of the SQL that the given column definition generates on the target platform. This way, the diff will be only generated if the column SQL representations on a given platform are different.

API changes

This is quite an invasive change but it's meant to be safe for a minor release. The changes in the test logic identify the existing bugs rather than breaking changes.

  1. The Comparator class now takes an optional AbstractPlatform argument. If it's passed, column comparison will be performed in the context of the platform. Not passing a platform is now deprecated.
  2. AbstractPlatform::columnsEqual() and Comparator::columnsEqual() have been added to compare column definitions in the context of the given platform. Depending on the platform, the comparison logic may be more complex than comparing the immediate column SQL. E.g. the default value on SQL Server is represented as a separate constraint.
  3. AbstractSchemaManager::createComparator() has been added to create the comparator. Besides the schemas being compared and the target platform, the comparison logic may depend on the data available only at runtime (current database collation on SQL Server).
  4. Constructors of the Comparator class and the subclasses are marked as internal to their schema managers. This will allow potentially improving the comparison logic without introducing breaking changes. The same is already implemented in driver-level connections and statements.

Platform-specific comparators clone the tables, if necessary, and normalize them before passing them to the base implementation. This implementation is far from perfect but it allows to avoid the introduction of a which may be not extensible enough and require a breaking change in the future.

Testing

Most of the changes in the tests are temporary and are needed to guarantee the parity between the old and the new implementation. The data providers will be removed in the next major release when the dependency of Comparator on AbstractPlatform becomes mandatory.

  1. All functional tests have been reworked to cover the default comparator and the one returned by $schemaManager->createSchemaManager().
  2. The unit tests that cover schema comparison in the context of a platform have been reworked to cover the default comparator and the one for the given platform (if it's different from the default).
  3. The unit tests covering the Comparator outside of the context of a platform have been extended to cover all sub-classes.

Future scope

  1. Array representation of column definitions. Burn with fire. Some of the existing APIs expect Column $column, others expect array<string,mixed> $column. The latter is an archaism, relies on some magic defaults (see AbstractPlatfrom::columnToArray()) and requires unnecessary type conversion back and forth.
  2. Deprecate and get rid of Comparator::diffColumn() and ColumnDiff::$changedProperties. In light of the above changes, the meaning and the purpose of the changed properties become ambiguous. If a property has changed but it doesn't result in a diff in the context of the current platform, should it be included in the changed properties?
  3. Mark SQLServer2012Platform::getDefaultConstraintDeclarationSQL() and other similar methods internal and then protected. See Inconsistent SchemaManager API across platforms #4503 for reference.
  4. Use parametrized classes instead of the temporarily added assertions.

$diff = (new Comparator())->diffTable($table, $diffTable);
self::assertNotFalse($diff);

self::assertEmpty($this->platform->getAlterTableSQL($diff));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two assertions cover the bug in the current implementation rather than the expected behavior. The original test and assertions read as follows:

  1. Make some schema changes that don't change the resulting SQL.
  2. Assert that the diff is not empty. This is the bug being fixed.
  3. Assert that even though the diff is not empty, the resulting diff SQL is. This is a workaround of the bug.

@morozov morozov force-pushed the platform-schema-comparison branch 2 times, most recently from 924f0ab to 489d210 Compare August 23, 2021 20:03
$diff = (new Comparator())->diffTable($table1, $table2);
self::assertNotFalse($diff);
self::assertEmpty($this->platform->getAlterTableSQL($diff));
self::assertFalse((new Comparator($this->platform))->diffTable($table1, $table2));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, the existing logic covers a bug, not the expected behavior.

src/Driver/AbstractMySQLDriver.php Show resolved Hide resolved
Schema $toSchema,
?AbstractPlatform $platform = null
) {
$comparator = new self($platform);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this still work, now that we have Comparator implementations for the individual drivers? This constructor call will yield a generic Comparator and not the implementation specific to the passed platform.

Copy link
Member Author

@morozov morozov Aug 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. The generic comparator supplied with a platform can cause false-positive diffs. It shouldn't be used this way. I'll also adjust the deprecation message in the comparator constructor accordingly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rolled this back and deprecated Schema::getMigrateFromSql() and ::getMigrateToSql() that instantiate a comparator internally. Introducing a dependency of Schema on SchemaManager would be incorrect, and they are just convenience methods that can be replaced with their implementation.

/** @var AbstractPlatform */
private $platform;

/** @var AbstractSchemaManager */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/** @var AbstractSchemaManager */
/** @var AbstractSchemaManager<MySQLPlatform> */

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will require Connection::createSchemaManager() and Driver::createSchemaManager() to be parametrized which will lead to the same issues as in #4746 (comment).


protected function setUp(): void
{
$this->comparator = new Comparator();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have deprecated not passing a platform to the constructor.

Copy link
Member Author

@morozov morozov Aug 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but it still has to be covered with a test until the deprecated code is removed.

@@ -42,7 +51,7 @@ public function testCompareSame1(): void

$expected = new SchemaDiff();
$expected->fromSchema = $schema1;
self::assertEquals($expected, Comparator::compareSchemas($schema1, $schema2));
self::assertEquals($expected, $this->comparator->compareSchemas($schema1, $schema2));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change? The method is still static, isn't it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is but now the comparator class is extended by platform-specific classes and I want to reuse the same test case to test all implementations. See the classes extending this one.

@morozov morozov force-pushed the platform-schema-comparison branch 2 times, most recently from 7ed68e7 to 26f64f4 Compare August 24, 2021 17:42
@morozov morozov added this to the 3.2.0 milestone Aug 24, 2021
greg0ire
greg0ire previously approved these changes Aug 25, 2021
Copy link
Member

@greg0ire greg0ire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, looks like this is going to be a huge improvement given the number of linked issues 🎉

@morozov
Copy link
Member Author

morozov commented Aug 25, 2021

The remaining four linked issues are indeed fixed by this PR (the details are in the comments to each issue). I unlinked two since they appear to be unrelated and need to be addressed separately. One more is not reproducible even on 3.1.x (closed).

@morozov morozov merged commit 0453eef into doctrine:3.2.x Aug 26, 2021
@morozov morozov deleted the platform-schema-comparison branch August 26, 2021 14:36
chalasr added a commit to symfony/symfony that referenced this pull request Jul 14, 2022
This PR was merged into the 4.4 branch.

Discussion
----------

[Messenger] Fix calls to deprecated DBAL methods

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | N/A
| License       | MIT
| Doc PR        | N/A

Follows doctrine/dbal#4746 and doctrine/dbal#4707.

Commits
-------

2f834f2 [Messenger] Fix calls to deprecated DBAL methods
morozov added a commit to morozov/dbal that referenced this pull request Aug 26, 2022
This code was relevant before the improvement in
doctrine#4746 was implemented. The new
comparator API is enforced in
doctrine#4771.

Now, the comparator will not produce a false-positive diff which is
enforced by the new test.
morozov added a commit to morozov/dbal that referenced this pull request Aug 26, 2022
This code was relevant before the improvement in
doctrine#4746 was implemented. The new
comparator API is enforced in
doctrine#4771.

Now, the comparator will not produce a false-positive diff which is
enforced by the new test.
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
3 participants