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

Improve forward compatibility between 2.x and 3.0 #4529

Merged
merged 1 commit into from
Mar 17, 2021

Conversation

mdumoulin
Copy link
Contributor

@mdumoulin mdumoulin commented Mar 4, 2021

Q A
Type feature
BC Break no
Fixed issues #4280

Summary

The current Doctrine\DBAL\Connection executeQuery(...) and executeCacheQuery() return a ResultStatement interface object. This PR try to improve forward compatibility between v2.x & v3.0 by adding some of missing features to Result returned by Connection :

  • fetchNumeric()
  • fetchAssociative()
  • fetchOne()
  • fetchAllNumeric()
  • fetchAllAssociative()
  • fetchAllKeyValue()
  • fetchAllAssociativeIndexed()
  • fetchFirstColumn()
  • iterateNumeric()
  • iterateAssociative()
  • iterateKeyValue()
  • iterateAssociativeIndexed()
  • iterateColumn()
  • rowCount()
  • columnCount()
  • free()

I used this version as reference : https://github.com/doctrine/dbal/blob/3.0.x/src/Result.php

Examples

Static analysis error

Here under, methods which throw a static analysis error in phpstan but actually work. This is all methods defined in Abstraction\Result interface :

/** @var Doctrine\DBAL\Connection $connection */
$connection->executeQuery('SELECT "something"')->fetchNumeric();
$connection->executeQuery('SELECT "something"')->fetchAssociative();
$connection->executeQuery('SELECT "something"')->fetchOne();
$connection->executeQuery('SELECT "something"')->fetchAllNumeric();
$connection->executeQuery('SELECT "something"')->fetchAllAssociative();
$connection->executeQuery('SELECT "something"')->fetchFirstColumn();
$connection->executeQuery('SELECT "something"')->iterateNumeric();
$connection->executeQuery('SELECT "something"')->iterateAssociative();
$connection->executeQuery('SELECT "something"')->iterateNumeric();
$connection->executeQuery('SELECT "something"')->iterateColumn();

The outpout pattern when running phpstan is the following :

Call to an undefined method Doctrine\DBAL\Driver\ResultStatement::fetchOne().

Not available methods

Here under, methods which throw undefined method fatal & static analyse errors. This is all methods added in Abstraction\V3Result interface :

$connection->executeQuery('SELECT "something"')->fetchAllKeyValue();
$connection->executeQuery('SELECT "something"')->fetchAllAssociativeIndexed();
$connection->executeQuery('SELECT "something"')->iterateKeyValue();
$connection->executeQuery('SELECT "something"')->iterateAssociativeIndexed();

The output pattern when trying to use one of those methods :

Fatal error: Uncaught Error: Call to undefined method Doctrine\DBAL\Driver\PDO\Statement::fetchAllKeyValue() in /var/www/test.php on line 41

Error: Call to undefined method Doctrine\DBAL\Driver\PDO\Statement::fetchAllKeyValue() in /var/www/test.php on line 41

Call Stack:
    0.0006     415576   1. {main}() /var/www/test.php:0

Implementation key points

The strategy is to :

  • Add a new interfaces with missing V3 Result features
  • Make Connection return an object implementing ResultStatement & V3Result interfaces

@mdumoulin mdumoulin force-pushed the improve-v2-forward-compatibility branch 2 times, most recently from 3173be4 to d1b7d7d Compare March 5, 2021 15:40
Copy link
Member

@beberlei beberlei left a comment

Choose a reason for hiding this comment

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

This is very important, however I think we should be bolder and move the V3Result to be Doctrine\DBAL\Result otherwise the API will change again in 3.0 and you cannot rely on typing the result tpye, example:

use Doctrine\DBAL\Result;

public function performQuery()
{
     $result = $this->connection->executeQuery('SELECT * FROM orders');
     return $this->convertQueryResult($result);
}

private function convertQueryResult(Result $result)
{
}

lib/Doctrine/DBAL/Abstraction/V3Result.php Outdated Show resolved Hide resolved
lib/Doctrine/DBAL/Connection.php Outdated Show resolved Hide resolved
@beberlei beberlei mentioned this pull request Mar 7, 2021
lib/Doctrine/DBAL/Abstraction/V3Result.php Outdated Show resolved Hide resolved
lib/Doctrine/DBAL/Connection.php Outdated Show resolved Hide resolved
lib/Doctrine/DBAL/Connection.php Outdated Show resolved Hide resolved
lib/Doctrine/DBAL/Driver/V3CompatStatement.php Outdated Show resolved Hide resolved
lib/Doctrine/DBAL/Driver/V3CompatStatement.php Outdated Show resolved Hide resolved
lib/Doctrine/DBAL/Driver/V3CompatStatement.php Outdated Show resolved Hide resolved
lib/Doctrine/DBAL/Connection.php Show resolved Hide resolved
lib/Doctrine/DBAL/Driver/FetchUtils.php Outdated Show resolved Hide resolved
lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php Outdated Show resolved Hide resolved
@beberlei beberlei added this to the 2.13.0 milestone Mar 8, 2021
@beberlei beberlei changed the base branch from 2.12.x to 2.13.x March 8, 2021 08:51
@mdumoulin mdumoulin force-pushed the improve-v2-forward-compatibility branch 5 times, most recently from 3a45490 to 42249a0 Compare March 8, 2021 15:24
lib/Doctrine/DBAL/Cache/ArrayStatement.php Outdated Show resolved Hide resolved
lib/Doctrine/DBAL/Cache/ArrayStatement.php Outdated Show resolved Hide resolved
lib/Doctrine/DBAL/Cache/ResultCacheStatement.php Outdated Show resolved Hide resolved
@mdumoulin
Copy link
Contributor Author

@morozov Drivers statements no longer implements wrapper level Result. It break some unit tests because of ResultStatement wrapper. The following code will no longer work for example.

$statement = $connection->executeQuery('SELECT "something"');

if ($statement instanceof ArrayStatement) {
    // True in 2.12.x version, false in this branch
}

I can't see any generic solution for this kind of edge case. Maybe we should let the user enable the Statement wrapper on purpose. We will avoid the risk of regression and allow to resolve deprecation on 2.x. It can be implemented by adding a ForwardCompatibility\Connection wrapper for example.

@morozov
Copy link
Member

morozov commented Mar 11, 2021

Maybe we should let the user enable the Statement wrapper on purpose. We will avoid the risk of regression and allow to resolve deprecation on 2.x. It can be implemented by adding a ForwardCompatibility\Connection wrapper for example.

If it's an opt-in layer, then there will be no improvement for static analysis, because the analyzers won't be able to detect the opt-in. As for the examples that currently don't work, e.g.:

$connection->executeQuery('SELECT "something"')->fetchNumeric();

There's already an alternative:

$connection->fetchNumeric('SELECT "something"');

@beberlei
Copy link
Member

Yes this edge case break is ok imho, the api is returning a statement, expecting a specific statement is not part of the interface

@mdumoulin mdumoulin force-pushed the improve-v2-forward-compatibility branch from ea422e9 to 9868993 Compare March 13, 2021 13:42
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.

Looks good.

@@ -238,25 +238,25 @@ public function testFetchingAllRowsSavesCache(callable $fetchAll): void
public static function fetchAllProvider(): iterable
{
yield 'fetchAll' => [
static function (ResultCacheStatement $statement): void {
static function (ForwardCompatibility\Result $statement): void {
Copy link
Member

Choose a reason for hiding this comment

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

The expected ResultCacheStatement type was unnecessarily specific. Let's loosen it to Driver\ResultStatement. If it had been implemented this way, it wouldn't require a change now.

$statement->fetchAllNumeric();
},
];

yield 'fetchFirstColumn' => [
static function (ResultCacheStatement $result): void {
static function (ForwardCompatibility\Result $result): void {
Copy link
Member

Choose a reason for hiding this comment

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

#4529 (comment) applies to all the above cases.

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 to understand here. ResultStament interface does not contain the fetchAllAssociative(), fetchAllNumeric() & fetchFirstColumn() methods. I can use the Doctrine\DBAL\Result interface, if we prefer not relying on concrete object.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, it should have been Driver\Result.

Copy link
Member

Choose a reason for hiding this comment

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

Alright, so the expected type was not unnecessarily specific: it was the type that implemented both of the old and new APIs. It still implements them but now the fetch*() methods return another class that implements both interfaces as well. Looks good 👍

@morozov
Copy link
Member

morozov commented Mar 16, 2021

@mdumoulin It looks good. I'd like all the changes to be squashed in one commit. Please do or otherwise, I'll do it later before the merge.

@mdumoulin mdumoulin force-pushed the improve-v2-forward-compatibility branch from ef2eb93 to 0beb8c2 Compare March 17, 2021 10:48
@mdumoulin
Copy link
Contributor Author

@morozov Great ! I just squashed all my commits in a single one.

Make Doctrine\DBAL\Connection executeQuery() & executeCachedQuery() return
a result implementing the following v3.0 features :
- fetchNumeric()
- fetchAssociative()
- fetchOne()
- fetchAllNumeric()
- fetchAllAssociative()
- fetchAllKeyValue()
- fetchAllAssociativeIndexed()
- fetchFirstColumn()
- iterateNumeric()
- iterateAssociative()
- iterateKeyValue()
- iterateAssociativeIndexed()
- iterateColumn()
- rowCount()
- columnCount()
- free()
@morozov morozov force-pushed the improve-v2-forward-compatibility branch from 0beb8c2 to 4297f50 Compare March 17, 2021 15:11
@morozov morozov merged commit b49127c into doctrine:2.13.x Mar 17, 2021
@morozov
Copy link
Member

morozov commented Mar 17, 2021

Thanks, @mdumoulin!

@mdumoulin
Copy link
Contributor Author

@morozov @beberlei Thanks again for your quick feedback & review.

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.

3 participants