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

Add Result::fetchAllKeyValue() and ::iterateKeyValue() #4293

Merged
merged 2 commits into from
Sep 26, 2020

Conversation

morozov
Copy link
Member

@morozov morozov commented Sep 24, 2020

Q A
Type improvement
BC Break no

Fixes #4289.

The behavior of the newly added methods is a replacement of PDO::FETCH_KEY_PAIR which is implemented at the wrapper level and is available for all supported drivers.

Unlike PDO which requires the result to have exactly two columns, DBAL will require at least two columns in order to allow the ROWNUM columns used by the platforms that do not natively support LIIMIT queries (Oracle, IBM DB2).

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.

Unlike PDO which requires the result to have exactly two columns, DBAL will require at least two columns in order to allow the ROWNUM columns used by the platforms that do not natively support LIIMIT queries (Oracle, IBM DB2).

Can you please elaborate on this? I'm unfamiliar with these platforms. Is that kind of column always returned in a result set?

@@ -379,6 +379,22 @@ Execute the query and fetch all results into an array:
)
*/

fetchAllKeyValue()
~~~~~~~~~~~~~~~~~~~~~
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
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~

@@ -425,6 +441,18 @@ Retrieve associative array of the first result row.

There are also convenience methods for data manipulation queries:

iterateKeyValue()
~~~~~~~~~~~~~~~~~~~~~
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
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~

@morozov
Copy link
Member Author

morozov commented Sep 24, 2020

Can you please elaborate on this? I'm unfamiliar with these platforms. Is that kind of column always returned in a result set?

Yes. It's appended to the inner query for counting rows and used by the outer query for filtering.

For instance, on DB2:

$conn = DriverManager::getConnection([
    'driver'   => 'ibm_db2',
    'user'     => 'db2inst1',
    'password' => 'Passw0rd',
    'dbname'   => 'doctrine',
]);

$sql = $conn->getDatabasePlatform()
    ->modifyLimitQuery('SELECT TEST_INT, TEST_STRING from FETCH_TABLE', 1, 1);

echo $sql, PHP_EOL; // formatted for readability
/*
SELECT db22.*
FROM (
    SELECT db21.*, ROW_NUMBER() OVER () AS DC_ROWNUM
    FROM (
        SELECT TEST_INT, TEST_STRING from FETCH_TABLE
    ) db21
) db22
WHERE db22.DC_ROWNUM >= 2
  AND db22.DC_ROWNUM <= 2
*/

$data = $conn->fetchAllAssociative($sql);
var_dump($data);
/*
array(1) {
  [0] =>
  array(3) {
    'TEST_INT' =>
    int(2)
    'TEST_STRING' =>
    string(3) "foo"
    'DC_ROWNUM' =>
    string(1) "2"
  }
}
*/

Despite the fact that we selected two columns, the result contains three.

UPD: I added a test for it.

@greg0ire
Copy link
Member

I see, the DBAL does that here:

return sprintf(
'SELECT db22.* FROM (SELECT db21.*, ROW_NUMBER() OVER() AS DC_ROWNUM FROM (%s) db21) db22 WHERE %s',
$query,
implode(' AND ', $where)
);

Thanks for the explanation, this looks good.

Good point about adding a test also, I think you discovered a bug 😅

greg0ire
greg0ire previously approved these changes Sep 24, 2020
@morozov
Copy link
Member Author

morozov commented Sep 24, 2020

I think you discovered a bug 😅

Yeah, 4½ years ago (#2374) 😅

@morozov
Copy link
Member Author

morozov commented Sep 24, 2020

@tswcode does it look like what you're looking for?

@tswcode
Copy link

tswcode commented Sep 24, 2020

Indeed, that looks promising. I will test it as soon as I am back at work (on monday). :)

Copy link
Member

@SenseException SenseException left a comment

Choose a reason for hiding this comment

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

My thoughts about the behaviour of these methods are written to the related lines of code.

I'm not sure about missing tests for the catched exceptions for rowCount() and columnCount(). There might be value that a DriverException is only internally handled and that the methods are converting them to the expected exceptions.

fetchAllKeyValue()
~~~~~~~~~~~~~~~~~~

Execute the query and fetch the first two columns into an associative array as keys and values respectively:
Copy link
Member

@SenseException SenseException Sep 24, 2020

Choose a reason for hiding this comment

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

From the docblock of fetchAllKeyValue:

The result must contain at least two columns.

SELECT key, value, foobar FROM my_table;

What happens to the rest of the columns? first is key, second is value, third (unrelated to rownumber) is... ? Is this defined or currently an open behaviour? If there is a fixed definition of what happens with every other column after the second, it should be put into the documentation. Like that they fetched but ignored by the methods.

This might be an interesting information for design decisions of users.

Copy link
Member Author

Choose a reason for hiding this comment

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

What happens to the rest of the columns?

Nothing. As well as nothing happens to other columns when the first column is fetched via fetchFirstColumn().

it should be put into the documentation

How would you put it?

Copy link
Member

@SenseException SenseException Sep 25, 2020

Choose a reason for hiding this comment

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

.. note::
   Any additional column to the key and value columns will be ignored by this method.

Copy link
Member Author

Choose a reason for hiding this comment

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

While I agree that this note is better than nothing, I wouldn't like the users to rely on this behavior just because it's currently implemented. How about this:

All additional columns will be ignored and are only allowed to be selected by DBAL for its internal purposes.

iterateKeyValue()
~~~~~~~~~~~~~~~~~

Execute the query and iterate over the first two columns as keys and values respectively:
Copy link
Member

Choose a reason for hiding this comment

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

Same as in my comment in fetchAllKeyValue doc.

tests/Functional/Connection/FetchTest.php Show resolved Hide resolved
@morozov
Copy link
Member Author

morozov commented Sep 24, 2020

I'm not sure about missing tests for the catched exceptions for rowCount() and columnCount(). There might be value that a DriverException is only internally handled and that the methods are converting them to the expected exceptions.

The tests are missing because there's no clear understanding of how these methods could throw an exception in any of the existing drivers. Not that it's impossible, they just were never added.

The general concept is that the driver-level methods may throw driver exceptions while the wrapper should convert them into a generic or specialized wrapper-level exception (e.g. ConnectionLost). The only reason I'm making this change now is that the new fetch mode calls columnCount() that doesn't handle the documented driver exception. But a method in the wrapper layer cannot throw a driver exception, so it must be handled. The only right place to handle the exception from the driver columnCount() is in the wrapper columnCount().

There's absolutely a lot of work on exception handling (#4160 is just the beginning).

@morozov morozov dismissed SenseException’s stale review September 26, 2020 16:51

The suggestions on improving the documentation have been addressed.

@morozov morozov merged commit 01b7a40 into doctrine:3.0.x Sep 26, 2020
@morozov morozov deleted the issues/4289 branch September 26, 2020 16:51
@morozov morozov removed the request for review from SenseException September 26, 2020 16:51
@morozov morozov self-assigned this Sep 26, 2020
@tswcode
Copy link

tswcode commented Oct 2, 2020

Unfortunately I was busy this week, I just noticed that this feature has been added to the branch 3.0.x.

@morozov Could you also add it to the branch 2.11.x ?

@morozov
Copy link
Member Author

morozov commented Oct 13, 2020

@tswcode see #4338.

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

Add a fetch mode methods for "PDO::FETCH_KEY_PAIR"
4 participants