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

Don't Set a Default Value on PostgreSQL SERIAL Fields #2907

Merged
merged 2 commits into from Nov 19, 2017

Conversation

chrisguitarguy
Copy link
Contributor

Specifically when 'notnull' => false is in the column options.

Right now DBAL adds a DEFAULT NULL when that happens. [BIG]SERIAL fields already have a default value and adding the DEFAULT NULL produces an error.

Fixes #2906

Not sure if the functional tests here are necessary, but it seemed like a good idea.

@chrisguitarguy
Copy link
Contributor Author

@lcobucci lcobucci added this to the 2.7 milestone Nov 12, 2017
Copy link
Member

@lcobucci lcobucci left a comment

Choose a reason for hiding this comment

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

Nice job, great commit organisation too.

I got some suggestions, questions, and nitpicking - obviously.

Can you please make sure that the code you're adding follows our standards? phpcs is already configured - with phpcbf - and can be helpful (I've tried to make it mandatory and simplify the lives of new contributors on #2849 but @Ocramius said NOPE 😂).

*/
public function getDefaultValueDeclarationSQL($field)
{
if (self::isSerialField($field) && empty($field['notnull'])) {
Copy link
Member

Choose a reason for hiding this comment

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

This condition will result to true when $field['notnull'] doesn't exist, is this the expected behaviour? I'd suggest to be more explicit about that, like $field['notnull'] ?? false === false.

Copy link
Member

Choose a reason for hiding this comment

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

I know that we use empty() on the parent method, but we didn't have these features back then.

Copy link
Member

Choose a reason for hiding this comment

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

Is there any reason to leave the empty($field['notnull']) out of the method?

Copy link
Member

Choose a reason for hiding this comment

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

Also do we have unit tests covering all the paths?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there any reason to leave the empty($field['notnull']) out of the method?

Yep. nonnull doesn't have anything to do with being a SERIAL field. This is perfectly okay: whatever SERIAL NOT NULL. So if someone wants to make sure the field is notnull they are free to do so. so it make more sense to me to check for a serial field as well as check notnull in one condition here. Could move it all into a isSerialFieldAndNotNull method, though.

This condition will result to true when $field['notnull'] doesn't exist, is this the expected behaviour?

$field['notnull'] missing is the same as $field['notnull'] = false yeah? I think you're right though, should be more explicit.

Also do we have unit tests covering all the paths?

I need to add some for whatever SERIAL NOT NULL now that I think about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, coming back to this notnull thing. I'm not actually it sure it should get checked at all. Any default value for a serial field is invalid -- they already have defaults built in. Maybe getDefaultValueDeclarationSQL should just return its empty string for any SERIAL field?

return parent::getDefaultValueDeclarationSQL($field);
}

private static function isSerialField($field)
Copy link
Member

Choose a reason for hiding this comment

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

Missing type declarations (parameters and return)

Copy link
Member

Choose a reason for hiding this comment

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

Also, any reason to make this static?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Missing type declarations (parameters and return)

Left out the parameter type declaration because the method from which this is called doesn't have one either (getDefaultValueDeclarationSQL). $field theoretically should be array? Happy to add the type declaration for $field, but that was the reason I didn't.

Definitely should have the return type!

Also, any reason to make this static?

Habit of my, sorry. I tend to make "pure" utility functions static -- since nothing from the platform's instance state is required, I made it static. Want me to make it non-static?

Copy link
Member

Choose a reason for hiding this comment

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

I got your point. I don't see much difference in this case, since the caller is already in the scope of the instance. I'd keep it non-static here but was just curious 😄


private static function isSerialField($field)
{
return !empty($field['autoincrement']) && isset($field['type']) && (
Copy link
Member

Choose a reason for hiding this comment

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

Same comments about being explicit here (with empty())


/**
* @dataProvider serialTypes
* @group https://github.com/doctrine/dbal/issues/2906
Copy link
Member

Choose a reason for hiding this comment

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

Please use only 2906 as group


/**
* @dataProvider serialTypes
* @group https://github.com/doctrine/dbal/issues/2906
Copy link
Member

Choose a reason for hiding this comment

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

Please use only 2906 as group

* @dataProvider serialTypes
* @group https://github.com/doctrine/dbal/issues/2906
*/
public function testAutoIncrementCreatesSerialDataTypesWithoutADefaultValueWheNotNullIsFalse($type)
Copy link
Member

Choose a reason for hiding this comment

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

public function testAutoIncrementCreatesSerialDataTypesWithoutADefaultValueWheNotNullIsFalse(string $type) : void


$columns = $this->_sm->listTableColumns($tableName);

$this->assertNull($columns['id']->getDefault());
Copy link
Member

Choose a reason for hiding this comment

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

Please use self::assert* instead

@@ -144,6 +144,30 @@ public function testGenerateTableWithAutoincrement()
self::assertEquals(array('CREATE TABLE autoinc_table (id SERIAL NOT NULL)'), $this->_platform->getCreateTableSQL($table));
}

public static function serialTypes()
Copy link
Member

Choose a reason for hiding this comment

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

Add : array as return type

* @dataProvider serialTypes
* @group https://github.com/doctrine/dbal/issues/2906
*/
public function testGenerateTableWithAutoincrementAndNotNullFalseDoesNotSetADefaultValueOnSerialFields($type, $definition)
Copy link
Member

Choose a reason for hiding this comment

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

Please add type declarations here


$sql = $this->_platform->getCreateTableSQL($table);

self::assertEquals(array("CREATE TABLE autoinc_table_notnull (id $definition)"), $sql);
Copy link
Member

Choose a reason for hiding this comment

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

Please use short-array notation

@chrisguitarguy
Copy link
Contributor Author

chrisguitarguy commented Nov 12, 2017

phpcs is already configured - with phpcbf - and can be helpful

I did the best I could with this. Found some violations in the tests with aligned equals signs. Running either tool on the whole codebase produces a lot of violations, even running it on the files I edited were 40 or 50 fixes per file.

I'm not actually it sure it should get checked at all. Any default value for a serial field is invalid -- they already have defaults built in.

I did make the change so notnull is no longer checked. I'm not sure if serial fields should error if a default is set? Or just silently ignore it as it does with the latest changes. Doesn't look like the default value SQL stuff is allowed to throw based on its docblocks.

@morozov
Copy link
Member

morozov commented Nov 12, 2017

Time for a shameless self-promotion:

pull-request doctrine dbal 2907 --standard=vendor/doctrine/coding-standard/lib/Doctrine

FILE: tests/Doctrine/Tests/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php
--------------------------------------------------------------------------------
FOUND 0 ERRORS AND 2 WARNINGS AFFECTING 2 LINES
--------------------------------------------------------------------------------
 159 | WARNING | Line exceeds 120 characters; contains 147 characters
 175 | WARNING | Line exceeds 120 characters; contains 149 characters
--------------------------------------------------------------------------------

@chrisguitarguy, see morozov/diff-sniffer-pull-request and morozov/diff-sniffer-pre-commit.

Copy link
Member

@lcobucci lcobucci left a comment

Choose a reason for hiding this comment

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

Great job @chrisguitarguy, just one more thing (apart from what @morozov said).

return parent::getDefaultValueDeclarationSQL($field);
}

private static function isSerialField($field) : bool
Copy link
Member

Choose a reason for hiding this comment

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

Regarding the type, we just don't have the type declaration on getDefaultValueDeclarationSQL for historical reasons. Let's add it here too

@chrisguitarguy
Copy link
Contributor Author

Thanks, @morozov, those tools are great!

Updated with the latest requested changes and fixed the PHP CS warnings, @lcobucci.

Postgres has a `SERIAL` data type to allow shorthand for
`nextval('sequence_name')`, but DDL like this:

    CREATE TABLE example (id SERIAL)

Cannot be generated in the DBAL schema APIs.
Serial fields already have a default value. By opting out of `notnull`
serial fields will just set the next value in the sequence.
@lcobucci lcobucci merged commit 51bf0ae into doctrine:master Nov 19, 2017
@lcobucci lcobucci self-assigned this Nov 19, 2017
lcobucci added a commit to lcobucci/dbal that referenced this pull request Nov 19, 2017
@lcobucci
Copy link
Member

@chrisguitarguy merged and backported to 2.6, thanks for your contribution!

@chrisguitarguy
Copy link
Contributor Author

My pleasure, thanks for the support @lcobucci!

@chrisguitarguy chrisguitarguy deleted the pg_serial_defaults branch August 4, 2022 15:54
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 5, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants