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

PostgreSQL 10 support #2893

Merged
merged 2 commits into from
Dec 26, 2017
Merged

PostgreSQL 10 support #2893

merged 2 commits into from
Dec 26, 2017

Conversation

simPod
Copy link
Contributor

@simPod simPod commented Oct 19, 2017

When using Postgres 10, DBAL throws

An exception occurred while executing 'SELECT min_value, increment_by FROM "schemaname"."table_id_seq"':

  SQLSTATE[42703]: Undefined column: 7 ERROR:  column "min_value" does not exist
  LINE 1: SELECT min_value, increment_by FROM "schemaname"."table_id_se...

This fixes it as PSQL10 reads sequences definitions from pg_sequences. https://www.postgresql.org/docs/10/static/view-pg-sequences.html

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.

Can you also add new tests that covers that for PGSQL 10?

@@ -289,7 +289,13 @@ protected function _getPortableSequenceDefinition($sequence)
$sequenceName = $sequence['relname'];
}

$data = $this->_conn->fetchAll('SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName));
$version = (float) $this->_conn->getWrappedConnection()->getServerVersion();
Copy link
Member

Choose a reason for hiding this comment

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

This is breaking the test suite, can you please check that?

Copy link
Member

Choose a reason for hiding this comment

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

Also Postgres 10 yet is not supported in Travis, so there's no way to 100% guarantee the compatibility at this moment.

@Majkl578
Copy link
Contributor

Duplicate of #2868. Waiting for Travis, see travis-ci/travis-ci#8537.

@simPod
Copy link
Contributor Author

simPod commented Oct 25, 2017

@lcobucci It's done. I believe the test was not working properly before (correct me if I'm wrong)

@simPod
Copy link
Contributor Author

simPod commented Oct 25, 2017

There was a case when null was returned for $data and this case was ignored. https://github.com/doctrine/dbal/pull/2893/files#diff-e4bd68e20fcb55598e13e0831ba281abR63 I can add null case it needed.

$data = $this->_conn->fetchAll('SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName));
$version = (float) $this->_conn->getServerVersion();

if ($version >= 10) {
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 this should rather be based on Platform check, there will sure be separate platform for Postgres 10. Anyway I think it should all be done in a single PR, not fixing random pieces randomly... :/

Copy link
Member

Choose a reason for hiding this comment

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

I agree, also exposing Connection#getServerVersion() a no-go IMO

@simPod
Copy link
Contributor Author

simPod commented Oct 25, 2017

Tried to refactor. I see this project's internals for the first time tho but IMO it's good start for PSQL10. I can either continue improving this or we can close.

@PillowPillow
Copy link

Any news about this PR?

@iquad
Copy link

iquad commented Nov 11, 2017

@PillowPillow Travis still working to implement Postgresql 10. Until that time, we have to wait.

@SCIF
Copy link

SCIF commented Nov 30, 2017

Just ran functional tests on Postgres10 localy and all good. 👍

@lcobucci
Copy link
Member

@SCIF it's really good to hear that let's just hope that travis adds support to it soon

@Majkl578
Copy link
Contributor

I got tired of infinitely waiting for Travis, so here is externally installed Postgres 10: #2946. Feel free to pick that commit and build a working compat PR on top. 😎

@@ -3570,4 +3570,9 @@ public function getStringLiteralQuoteCharacter()
{
return "'";
}

public function getSequenceDataSQL($sequenceName, $schemaName)
Copy link
Contributor

Choose a reason for hiding this comment

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

DBAL requires PHP 7.1, please add parameter and return types where applicable.

Copy link
Contributor

Choose a reason for hiding this comment

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

It should probably be renamed to getSequenceMetadataSQL, data is not explicit.

@@ -0,0 +1,37 @@
<?php
/*
Copy link
Contributor

Choose a reason for hiding this comment

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

No license headers please, we've already removed them in develop.

@@ -67,7 +67,7 @@ protected function getDatabasePlatformsForVersions()
array('9.4', 'Doctrine\DBAL\Platforms\PostgreSQL94Platform'),
array('9.4.0', 'Doctrine\DBAL\Platforms\PostgreSQL94Platform'),
array('9.4.1', 'Doctrine\DBAL\Platforms\PostgreSQL94Platform'),
array('10', 'Doctrine\DBAL\Platforms\PostgreSQL94Platform'),
array('10', 'Doctrine\DBAL\Platforms\PostgreSQL100Platform'),
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 ::class.

public function filtersSequencesDataProvider()
{
return [
['Doctrine\DBAL\Platforms\PostgreSqlPlatform'],
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 ::class.

@Majkl578
Copy link
Contributor

Here's a diff of keywords in 9.6 and 10: https://www.diffchecker.com/fJ0YkHgw
There are no new reserved keywords, but some of the existing are not reserved anymore. But I'm not a DBAL guy so I can't say whether they are supposed to be removed in PostgreSQL100Keywords or not.

@simPod
Copy link
Contributor Author

simPod commented Dec 21, 2017

They were not reserved before tho.

return PostgreSQL100Keywords::class;
}

public function getSequenceMetadataSQL(string $rawSequenceName, string $schemaName) : string
Copy link
Member

@morozov morozov Dec 22, 2017

Choose a reason for hiding this comment

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

There shouldn't be a semantic difference between this method and its parents from AbstractPlatform and PostgreSqlPlatform. Otherwise, the caller which depends on the abstract interface will not know whether it should pass raw or non-raw sequence name.

@@ -289,7 +289,7 @@ protected function _getPortableSequenceDefinition($sequence)
$sequenceName = $sequence['relname'];
}

$data = $this->_conn->fetchAll('SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName));
$data = $this->_conn->fetchAll($this->_platform->getSequenceMetadataSQL($sequenceName, $sequence['schemaname']));
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 really necessary to fetchAll() and then use only $data[0]? Doing fetch() and using the row directly would make more sense to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's no method fetch() in connection. In my opinion, this is ok as it fetches one row

Copy link
Contributor

Choose a reason for hiding this comment

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

There are more fetch*() methods though, fetchArray() is the one you need.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The data then looks like
image.
I personally think the current state is more verbose as I have to reference array values by column names and not by numeric indeces.

{
$sequenceName = $this->cleanSequenceNameFromSchemaName($rawSequenceName, $schemaName);

return 'SELECT min_value, increment_by FROM pg_sequences WHERE schemaname = '.$this->quoteStringLiteral($schemaName).' AND sequencename = ' . $this->quoteStringLiteral($sequenceName);
Copy link
Member

Choose a reason for hiding this comment

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

Given that in PostgreSQL 10, the sequence metadata is available via a view, would it make sense to rework getListSequencesSQL() instead? With this approach, the client will not have to execute the SQL from getSequenceMetadataSQL() for each sequence, and the method itself will not need to be introduced on the abstract platform level.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure it's a good idea. getListSequencesSQL() lists sequences. It would need to add also increment_by and min_value columns. That would change the method's functionality. This method is called before in listSequences() already.

Copy link
Member

Choose a reason for hiding this comment

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

Look at the OraclePlatform. Its getListSequencesSQL() returns list and metadata. The only reason it doesn't do the same for PostgreSQL is that prior to version 10 there was no view to fetch the metadata from.

@iquad
Copy link

iquad commented Dec 22, 2017

@simPod are you going to fix/apply or need help ? We're waiting to deploy new product to production for 2 months :P I can help if you wish.

@simPod
Copy link
Contributor Author

simPod commented Dec 22, 2017

@iquad you can already use this fork so you don't have to wait :)

@iquad
Copy link

iquad commented Dec 22, 2017

@simPod yeah, but need doctrine to merge them, server tests, 30+ development machine etc. Can't handle them one by one :/


$sequence1Name = 'sequence_1';
$sequence2Name = 'sequence_2';
$sequence1 = new Sequence($sequence1Name);
Copy link
Member

Choose a reason for hiding this comment

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

@simPod please make sure all sequence attributes are covered by the test, not only the name.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you review now please? Thanks

$actualSequences[$sequence->getName()] = $sequence;
}

$isOracle = $this->_sm->getDatabasePlatform()->getName() === OraclePlatform::NAME;
Copy link
Member

Choose a reason for hiding this comment

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

Oracle is not the only supported platform which returns upper-cased column names. DB2 is the other one but it looks DBAL doesn't support sequences in DB2. In any event, checking the platform name is not the best approach.

I can think of two solutions:

  1. Figure out why the rest of the tests in this class don't fail on Oracle without using the portability layer. For instance, testListTableColumns() expects column names to be lower-cased, however, they are upper-cased in Oracle. Must be implemented in:
    $name = strtolower($column->getQuotedName($this->_platform));
  2. Use Portability\Connection with PORTABILITY_FIX_CASE and fetch_case = CASE_LOWER when instantiating the schema manager. This is hor a real production code would deal with the case discrepancy.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

testListTableColumns also converts table name to lowercase self::assertEquals('test', strtolower($columns['test']->getname()));

Can do it this way then. In

$name = strtolower($column->getQuotedName($this->_platform));
it just creates an array with lowercased table names as keys.

Copy link
Member

Choose a reason for hiding this comment

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

It would be a breaking change. Doing strtolower() in a test would mean that the production code should do the same by design which it not true, it's a bug.

Let's skip the test for Oracle for now and fix the schema manager in 3.0.

$sequence1InitialValue = 1;
$sequence2Name = 'sequence_2';
$sequence2AllocationSize = 2;
$sequence2InitialValue = 2;
Copy link
Member

Choose a reason for hiding this comment

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

It's better to use different values for different parameters to make sure they are not confused during deployment or introspection.


$this->assertSame($sequence2Name, $actualSequence2->getName());
$this->assertEquals($sequence2AllocationSize, $actualSequence2->getAllocationSize());
$this->assertEquals($sequence2InitialValue, $actualSequence2->getInitialValue());
Copy link
Member

Choose a reason for hiding this comment

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

Not sure the assertions are intentionally this verbose, but the same could be checked in a more compact way:

$sequence1 = new Sequence('sequence_1', 1, 2);
$sequence2 = new Sequence('sequence_2', 3, 4);

// create the sequences

self::assertArraySubset([
    $sequence1,
    $sequence2,
], $this->_sm->listSequences());

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm fan of verbosity but can remake if you think it better suits code style of this project. Should I proceed then? :)

Copy link
Member

Choose a reason for hiding this comment

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

Depending on what's the purpose of this verbosity. The approach I suggested is more state-based. It only tests that the collection returned by listSequences() contains the objects which were previously created. Isn't it what it's supposed to do?

The current approach also tests the public API of the Sequence class. For instance, if there was some logic in getInitialValue and getAllocationSize(), the test would have to take this logic into account as well which would make it less stable.

There are no strict coding guidelines in this regard, so it's up to you how you want to structure the test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. However, assertArraySubset asserts by keys and there are other Sequences created in db as well.

image

image

I reworked it a bit

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's failing on Psql 9.5. I'd have to either sort it by name or revert to the previous verbose version.

Copy link
Member

Choose a reason for hiding this comment

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

Eh… I didn't know assertArraySubset() is strict about numeric keys. The last approach could fail on any DB because the order of sequences is not enforced.

The problem is not verbosity itself, the problem is also that having the logic like array_map() or array_filter() outside of assertions which makes the test less readable and less clear about which exactly case it covers.

How about this:

$list = $sm->listSequences();
self::assertContains($sequence1, $list);
self::assertContains($sequence2, $list);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That wouldn't work as those are different objects.

I guess in the end this is the simplier solution:

$this->assertSame($sequence1Name, $actualSequence1->getName());
$this->assertEquals($sequence1AllocationSize, $actualSequence1->getAllocationSize());
$this->assertEquals($sequence1InitialValue, $actualSequence1->getInitialValue());

Copy link
Member

Choose a reason for hiding this comment

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

Okay, let's leave it like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Kk, it's there

Copy link
Contributor

@Majkl578 Majkl578 left a comment

Choose a reason for hiding this comment

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

Just last few nitpicks, otherwise LGTM. 👍

$data = $this->_conn->fetchAll('SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName));
if ( ! isset($sequence['increment_by'], $sequence['min_value'])) {
$data = $this->_conn->fetchAssoc('SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName));
$sequence = array_merge($sequence, $data);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is array_merge needed here? Wouldn't + just work since it's assoc?

$actualSequence1 = $actualSequences[$sequence1Name];
$actualSequence2 = $actualSequences[$sequence2Name];

$this->assertSame($sequence1Name, $actualSequence1->getName());
Copy link
Contributor

Choose a reason for hiding this comment

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

Please call all asserts statically.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Isn't that deprecated?

Copy link
Contributor

Choose a reason for hiding this comment

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

What do you mean? All assert methods are declared static: https://github.com/sebastianbergmann/phpunit/blob/master/src/Framework/Assert.php

Copy link
Contributor Author

@simPod simPod Dec 26, 2017

Choose a reason for hiding this comment

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

Ah, not deprecated but it's there because of something old sebastianbergmann/phpunit#1914 (comment).

Both can be used tho. I'm used to call it via non-static way.

I guess it needs to be reworked to static then as the whole project uses static calls. Shall I?

Copy link
Contributor

Choose a reason for hiding this comment

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

And bad design in PHPUnit apparently, since non-static TestCase extends static Assert class. :/
Since it's declared as static, it was decided to use static calls in tests for assert methods, otherwise it's different context (and personally I think PHP should emit a warning, just like static->instance call).

@simPod
Copy link
Contributor Author

simPod commented Dec 26, 2017

@morozov thanks for help!

@simPod
Copy link
Contributor Author

simPod commented Dec 26, 2017

@lcobucci is there anything else?

Copy link
Member

@Ocramius Ocramius left a comment

Choose a reason for hiding this comment

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

LGTM 🚢

@Ocramius Ocramius added this to the 2.7.0 milestone Dec 26, 2017
@Ocramius Ocramius merged commit fcc00de into doctrine:master Dec 26, 2017
@sergiors
Copy link

OMG!

@iquad
Copy link

iquad commented Dec 29, 2017

thank you so much, all of you!

@anonim1133
Copy link

Is there any date for 2.7 so I could use postgres 10? Or is there a way to patch 2.6 with this commit?

@Ocramius
Copy link
Member

Ocramius commented Jan 2, 2018

No date yet. You can already use 2.6 with PostgreSQL 10, but the schema tools won't be working correctly yet.

xbg pushed a commit to xbg/dbal that referenced this pull request Feb 20, 2018
Use this before 2.6 branch will be patched doctrine#2893
xbg pushed a commit to xbg/dbal that referenced this pull request Feb 22, 2018
Use this before 2.6 branch will be patched doctrine#2893
@Majkl578 Majkl578 mentioned this pull request Mar 28, 2018
@nicholasruunu
Copy link

Still have this issue with v2.9.2 and postgres 10.10

@exebece
Copy link

exebece commented Oct 1, 2019

I think this issue can easily be solved using proper doctrine connection string specifying the version of postgres like: DATABASE_URL=postgres://user@127.0.0.1:5432/db_name?charset=utf8&serverVersion=10

the serverVersion part helped me solving all issues with Doctrine & Postgres. Try it for free ;)

@nicholasruunu
Copy link

@exebece Yeah, my bad, updated the doctrine configuration from 9.x to 10 and it works just fine now. Thanks.

@XanderKon
Copy link

@exebece Thanks! Spent two hours and your comment save me from another time wasting.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 5, 2022
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.