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

DBAL-2925: Added functional test checking ability to change PK for po… #2929

Closed
wants to merge 8 commits into from

Conversation

SCIF
Copy link

@SCIF SCIF commented Nov 30, 2017

…stgres platform

@SCIF SCIF force-pushed the bugfix/DBAL-2925 branch 4 times, most recently from 7d1bdee to 6333a15 Compare December 5, 2017 10:34
@@ -3570,4 +3575,14 @@ public function getStringLiteralQuoteCharacter()
{
return "'";
}

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the phpdoc is redundant

Copy link
Author

Choose a reason for hiding this comment

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

Hi @simPod ,

Yeah, you are correct, it's totally useless and I will drop it soon.

@PapsOu
Copy link

PapsOu commented Jan 12, 2018

@simPod Does this PR is ready to be merged ? I've got the same problem for PK under postgresql.

@Majkl578
Copy link
Contributor

Majkl578 commented Jan 12, 2018

@PapsOu Unfortunately nobody has simply got to review it yet.

@@ -752,6 +752,11 @@ public function getDropForeignKeySQL($foreignKey, $table)
return $this->getDropConstraintSQL($foreignKey, $table);
}

protected function getDropPrimaryKeySQL(string $table) : string
{
return $this->getDropConstraintSQL("{$table}_pkey", $table);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use string concatenation here + ' instead of variable in string.

Copy link
Member

Choose a reason for hiding this comment

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

I found a couple of articles explaining that this is the way how the PK should be dropped on PostgreSQL but before seeing the explanation, this approach looks like using a "magic" suffix.

It would be nice if a link to documentation describing this approach was provided in the doc-block.


$c = new Comparator();
$diff = $c->diffTable($tableFrom, $tableTo);
self::assertInstanceOf(TableDiff::class, $diff, "There should be a difference and not false being returned from the table comparison");
Copy link
Contributor

Choose a reason for hiding this comment

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

Constant strings should be enclosed in '.

Copy link
Member

Choose a reason for hiding this comment

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

There should be a difference and not false

It's a very vague assertion. I'd omit it together with the previous one about the autoincrement and only focus on the end result where the new primary key is (id).

@Majkl578 Majkl578 requested a review from morozov January 12, 2018 15:15
/**
* @group 2925
*/
public function testAlterTableChangePrimaryKey() : void
Copy link
Member

Choose a reason for hiding this comment

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

Please move this test to SchemaManagerFunctionalTestCase. It should be checked and work the same for all platforms, not only for PostgreSQL.


$c = new Comparator();
$diff = $c->diffTable($tableFrom, $tableTo);
self::assertInstanceOf(TableDiff::class, $diff, "There should be a difference and not false being returned from the table comparison");
Copy link
Member

Choose a reason for hiding this comment

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

There should be a difference and not false

It's a very vague assertion. I'd omit it together with the previous one about the autoincrement and only focus on the end result where the new primary key is (id).

@@ -2065,6 +2065,11 @@ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
$sql[] = $this->getDropIndexSQL($index, $tableName);
}
foreach ($diff->changedIndexes as $index) {
if ($index->isPrimary()) {
$sql[] = $this->getDropPrimaryKeySQL($tableName);
Copy link
Member

Choose a reason for hiding this comment

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

@Majkl578 is there a way to enforce a new line before flow control operators like return, continue, etc on the code sniffer level? It would be very nice to have.

Copy link
Contributor

Choose a reason for hiding this comment

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

No sniff I'm aware of, but shouldn't be hard to implement. Just needs to be compatible i.e. with single statements:

if (...) {
    continue;
}

or comments:

foo();

// comment
return 123;

Copy link
Member

@morozov morozov Jan 12, 2018

Choose a reason for hiding this comment

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

Yeah. More generically, the first case is if the previous line has flow control statement which is a block opener, there shouldn't be an empty line. Like:

function x()
{
    foreach (...) {
        foreach (...) {
        }
    }

    foreach (...) {
    }
}

Copy link
Author

Choose a reason for hiding this comment

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

I've added new line before continue.

Copy link
Member

Choose a reason for hiding this comment

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

@carusogabriel do you want to file this as a requirement for coding standards (I see we have disabled issues for the repository). It'd be nice to have a reference point somewhere. Otherwise, I constantly forget where the discussion started.

/cc @deeky666

Copy link
Contributor

Choose a reason for hiding this comment

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

@morozov Require new lines before control structions you mean? :)

Copy link
Member

Choose a reason for hiding this comment

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

Yes. Before and after.

Copy link
Author

Choose a reason for hiding this comment

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

@morozov , Totally agree with this proposal, but code marked as still required your review :) Mark it properly please.

Copy link
Member

@morozov morozov Mar 30, 2018

Choose a reason for hiding this comment

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

@SCIF it's not because of the formatting issue. I want to see if the behavior of dropping a non-existing PK on SQL Server is consistent with other platforms.

Copy link
Author

Choose a reason for hiding this comment

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

This thread is regarding cs only, the PK ones here

@@ -752,6 +752,11 @@ public function getDropForeignKeySQL($foreignKey, $table)
return $this->getDropConstraintSQL($foreignKey, $table);
}

protected function getDropPrimaryKeySQL(string $table) : string
{
return $this->getDropConstraintSQL("{$table}_pkey", $table);
Copy link
Member

Choose a reason for hiding this comment

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

I found a couple of articles explaining that this is the way how the PK should be dropped on PostgreSQL but before seeing the explanation, this approach looks like using a "magic" suffix.

It would be nice if a link to documentation describing this approach was provided in the doc-block.

$this->_sm->dropAndCreateTable($tableFrom);

$tableFrom = $this->_sm->listTableDetails('primary_key_userid');
self::assertTrue($tableFrom->getColumn('user_id')->getAutoincrement());
Copy link
Author

@SCIF SCIF Feb 20, 2018

Choose a reason for hiding this comment

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

I can see that this test is failed on Oracle. Should I skip this assert for Oracle or does whole test make sense for Oracle?

Copy link
Member

Choose a reason for hiding this comment

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

@SCIF if the test is about being able to change PK, why does it use/verify autoincrement? Please try to not use it and use getPrimaryKey() instead.

@SCIF SCIF force-pushed the bugfix/DBAL-2925 branch 3 times, most recently from 842c6ac to 5e886df Compare March 3, 2018 08:59
@SCIF
Copy link
Author

SCIF commented Mar 30, 2018

Hi @Majkl578 , @morozov ,

I've fixed MsSQL and MySQL issues discovered after moving test to common Schema testcase.

Copy link
Member

@morozov morozov left a comment

Choose a reason for hiding this comment

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

@SCIF thanks for the cross-platform test. As you can see, it's already bringing value :-)

Please see my comments and address the code style issues.


protected function getDropPrimaryKeySQL(string $table) : string
{
return "
Copy link
Member

Choose a reason for hiding this comment

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

Could you use heredoc syntax instead?

SELECT @sql = 'ALTER TABLE ' + @table + ' DROP CONSTRAINT ' + name + ';'
FROM sys.key_constraints
WHERE [type] = 'PK'
AND [parent_object_id] = OBJECT_ID(@table);
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to move the object ID outside of the @sql so that the function is not invoked for every single PK in sys.key_constraints?

How will this query behave if the table doesn't have a PK? I'd expect sp_executeSQL be invoked with an empty argument and throwing some sort of exception.

Copy link
Author

Choose a reason for hiding this comment

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

Emm. If you will try to drop PK for any other platforms on table not containing it, exception will be thrown as well, so I don't see any inconsistency here.

Copy link
Member

Choose a reason for hiding this comment

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

Is it the same type of exception?

Copy link
Author

Choose a reason for hiding this comment

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

Hmm, different one, you are right. Just syntax error vs can't drop unexisted PK. What's your proposal? I don't have windows pc and that is my very first mssql experience in life :)

Copy link
Member

Choose a reason for hiding this comment

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

You don't need a Windows PC. The SQL Server extension can be compiled for PHP on Linux, and the server itself can be running in a Docker container (microsoft/mssql-server-linux).

I don't have a proposal currently. My only concern is this code may execute knowingly syntactically invalid SQL. Instead of doing the in the dynamic SQL way, can we just rely on some conventional PK name in SQL Server (like DROP CONSTRAINT PK_$table; from here)?

Copy link
Member

Choose a reason for hiding this comment

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

Hold on. getDropPrimaryKeySQL() is not even part of the abstract platform API. Why are we using it in the abstract platform code? From what I see, most of the platforms are capable of identifying PK by name including SQL Server. Why should we take special care about the PK on SQL Server if it can be just dropped by name same as other constraints?

Copy link
Author

Choose a reason for hiding this comment

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

We can't rely on any name conventions/best practices. As far as I investigated, the name builds according to schema PK_{$tableName}_{$someHash} and can't be predicted.

@@ -2078,6 +2078,12 @@ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
$sql[] = $this->getDropIndexSQL($index, $tableName);
}
foreach ($diff->changedIndexes as $index) {
if ($index->isPrimary()) {
Copy link
Member

Choose a reason for hiding this comment

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

The Scrutinizer failure reveals this as a decent candidate for refactoring in the future. Instead of the platform knowing how to drop a given index based on its properties (primary, non-primary and so on), the index should tell the platform how to drop itself (sort of a Visitor pattern):

interface Index
{
    function drop(Platform $platform);
}

interface Platform
{
    function dropIndex(string $name);
    function dropPrimaryKey(string $tableName);
}

This is outside of scope, just noting down.

/cc @Ocramius @deeky666

Copy link
Author

Choose a reason for hiding this comment

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

I was thinking about it but that will lead to creation of platform specific indices. Quite heavy refactoring will be required, I worry. I'm afraid I'm not able to do it in reasonable time.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not really fan of the suggested visitor approach. Index would then need to know how to represent itself on all existing platforms and even those not provided by DBAL out-of-box.

Copy link
Member

Choose a reason for hiding this comment

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

@SCIF no need to worry about it right now. It's just for the record.

Copy link
Member

Choose a reason for hiding this comment

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

Not really fan of the suggested visitor approach. Index would then need to know how to represent itself on all existing platforms and even those not provided by DBAL out-of-box.

Not exactly. They will need to be able to identify themselves as a type while the platform will know how to handle certain types. Same as we do with column types.


protected function getDropPrimaryKeySQL(string $table) : string
{
return 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY';
Copy link
Member

@morozov morozov Mar 31, 2018

Choose a reason for hiding this comment

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

I see, getDropPrimaryKeySQL() is being introduced here for an abstract platform but it's only needed for MySQL and has MySQL-specific syntax. This is a no-go.

I believe, the original issue (#2925) is filed for MySQL where DROP INDEX PRIMARY ON $table is not supported. Instead of introducing this method, MySqlPlatform::getDropIndexSQL() should be updated to produce correct SQL for the PK.

Copy link
Author

Choose a reason for hiding this comment

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

The issue originally discovered during work with Postgres. Initially I tried to modify getDropIndexSQL() method but it can't be done for postgres at least, so separate method for primary keys only is the best solution.

Copy link
Author

Choose a reason for hiding this comment

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

PK is not just an index, but it's a constraint in some RDBMS. Handling could be done by changing getDropIndexSQL() signature but I believe it's not the best option.

Copy link
Member

Choose a reason for hiding this comment

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

so separate method for primary keys only is the best solution

It's fine to have it for Postgres and MySQL which seem unable to drop the PK by name but it shouldn't be part of the abstract platform API. Those platforms which can drop PK by its name shouldn't have any changes.

Copy link
Author

@SCIF SCIF Apr 1, 2018

Choose a reason for hiding this comment

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

I really don't understand what you propose. Do you mean define getDropPrimaryKeySQL as:

protected function getDropPrimaryKeySQL(string $table) : string
{
    $this->getDropIndexSQL('primary', $table);
}

?
I can't get rid of getDropPrimaryKeySQL in favour getDropIndexSQL() because $index defined as Index|string. So, BC break required, if you would like to use only getDropIndexSQL().

Copy link
Member

Choose a reason for hiding this comment

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

Then, in the case when $index is a string, you get its object representation and see if it's primary or not.

Copy link
Member

Choose a reason for hiding this comment

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

You also can fix it only for your use case when the $index comes as an object from the schema manager. If there's another case when dropping the PK by name is required, it can be solved separately.

Copy link
Author

Choose a reason for hiding this comment

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

@morozov

Then, in the case when $index is a string, you get its object representation and see if it's primary or not.

How I can do that? I don't know a way to do it. If you mean to find a workaround for particular getAlterTableSQL() usage — yes, I can fix it in another way, but it's bloody stupid idea to find a big issue and solve only one of use-cases rather than fix it in principal. I've spent a lot of time on supporting this PR and would like to solve a root of evil, rather than just superficial solution of the problem.

Copy link
Member

Choose a reason for hiding this comment

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

How I can do that? I don't know a way to do it.

Neither do I. That's why I'm saying it's fine if you solve only the case where the passed index is an object.

I've spent a lot of time on supporting this PR and would like to solve a root of evil, rather than just superficial solution of the problem.

The root can could be only solved by reworking the entire index-related API (I recall there was a discussion or at least mention of that /cc @deeky666). This API would account for all differences between indices, constraints, primary keys, foreign keys, etc. in different platforms and would provide some unified higher level API.

It's impossible to solve it correctly in terms of the current API without introducing more controversial methods (I'm talking about getDropPrimaryKeySQL()). You can either solve your case in 2.x or postpone it until 3.x and solve properly.

Copy link
Author

@SCIF SCIF Apr 8, 2018

Choose a reason for hiding this comment

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

Right now, Index class contain primary property so, really not necessary to be revised because reflect it's purpose pretty fine. The problem is that index can be supplied by name, which would be my proposal to drop in 3.x.
@morozov , thanks for your input. I will refactor this PR just to fix comparing issue soon.

@SCIF
Copy link
Author

SCIF commented Mar 31, 2018

Travis fails on getting https://dev.mysql.com/get/mysql-apt-config_0.8.9-1_all.deb which can be downloaded for me.

@Majkl578
Copy link
Contributor

Travis issue reported in travis-ci/travis-ci#9421.

Base automatically changed from master to 4.0.x January 22, 2021 07:43
@morozov morozov closed this Oct 26, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants