Fix last insert ID consistency across drivers#3074
Fix last insert ID consistency across drivers#3074morozov wants to merge 26 commits intodoctrine:masterfrom
Conversation
71b1239 to
547f921
Compare
547f921 to
f9cf17a
Compare
|
Looks like we have a connection leak in general and especially with PDO drivers. Here's the number of MySQL connections obtained from
|
d335808 to
6fb67c9
Compare
|
Almost there. Down to one failure on SQL Server. |
…rever holes have been forgotten
5746e9f to
8d1ebf7
Compare
Majkl578
left a comment
There was a problem hiding this comment.
I am not sure about the possible BC impact especially around those type changes (making everything string). :|
| return $this->value; | ||
| } | ||
|
|
||
| public function register(string $value) : void |
There was a problem hiding this comment.
I'm not a fan of having this crate mutable.
There was a problem hiding this comment.
We can make it immutable. Would it make more sense to rename this to something like with() : self in this case? What about get()? Should it be something like toValue() instead?
There was a problem hiding this comment.
@Majkl578, sadly, the immutable approach will not just work since MysqliConnection and MysqliStatement share the same instance of LastInsertId. It's probably done like this to mitigate:
MySQL drivers reset the last insert ID to
0after each non-insert query
It could be worked around by keeping the reference to the connection from the statement. But this approach stinks too (also implemented in PDO Statement/Connection pair).
| class PDOConnection extends PDO implements Connection, ServerInfoAwareConnection | ||
| { | ||
| /** @var string */ | ||
| private $lastInsertId = '0'; |
There was a problem hiding this comment.
This default value + doc block don't seem valid since you assign LastInsertId below.
| $this->sql .= self::LAST_INSERT_ID_SQL; | ||
| $this->lastInsertId = $lastInsertId; | ||
| } | ||
| $this->lastInsertId = $lastInsertId; |
There was a problem hiding this comment.
Shouldn't this use Driver\LastInsertId too instead of Driver\SQLSrv\LastInsertId? For BC, Driver\SQLSrv\LastInsertId can exend Driver\LastInsertId and be deprecated.
[same for SQLSrvConnection]
|
|
||
| private function trackLastInsertId() : void | ||
| { | ||
| if (! $this->lastInsertId) { |
| } | ||
|
|
||
| return $stmt->fetchColumn(); | ||
| return $stmt->fetchColumn() ?: '0'; |
There was a problem hiding this comment.
I don't like?: here, it hides possible error values (would that be false, '0' or 0 here?) - is it possible to use !== false for example?
There was a problem hiding this comment.
In the original proposal, lastInsertId() suppresses errors everywhere by design (see the OCI implementation specifically). Not sure I understand and agree with it. We definitely could improve here.
| { | ||
| if (null === $name) { | ||
| return sasql_insert_id($this->connection); | ||
| return (string) sasql_insert_id($this->connection); |
There was a problem hiding this comment.
Looks BC break-ish, what returned false before would now return ''.
| } | ||
|
|
||
| return $this->query('SELECT ' . $name . '.CURRVAL')->fetchColumn(); | ||
| return (string) $this->query('SELECT ' . $name . '.CURRVAL')->fetchColumn(); |
There was a problem hiding this comment.
Looks BC break-ish, what returned false before would now return ''.
| * | ||
| * @throws \PDOException | ||
| */ | ||
| public function trackLastInsertId() : void |
There was a problem hiding this comment.
The whole design decision behind this method is a bit shady - making subclasses override/disable this behavior looks a bit weird. Would love to see this extracted into some InsertIdStrategyTracking with multiple providers (no-op/identity/...).
| { | ||
| if ($name === null) { | ||
| return false; | ||
| return '0'; |
There was a problem hiding this comment.
Looks BC break-ish, what returned false before would now return '0'.
| } | ||
|
|
||
| return (int) $result; | ||
| return (string) $stmt->fetchColumn(); |
There was a problem hiding this comment.
Looks BC break-ish, what returned 0 before would now return '0'.
…rever holes have been forgotten
…rever holes have been forgotten
|
Given the amount of remaining work which is entirely about the API and BC-related concerns, I propose to move it out of |
| * Returns the ID of the last inserted row or sequence value. | ||
| * | ||
| * If no sequence name is given, "0" is returned if there currently is no last insert ID available | ||
| * in the current session or in case of an error or if the driver does not support this operation. |
There was a problem hiding this comment.
@Majkl578 this is where the error suppression is explicitly defined. It looks dangerous to me that the return value may be different from what's expected because of a runtime issue.
|
@morozov agreed, pushing this back into |
… with a hardcoded value
…handles per test)
…rever holes have been forgotten
9fa28d2 to
007c327
Compare
007c327 to
6894505
Compare
|
I'm closing this as "won't fix" and not a bug. Below is the explanation of what exactly was attempted to get fixed and why it shouldn't. Inconsistency in when the last inserted ID is available across platformsThis is what the largest part of the code change is about including the introduction of the This issue is being fixed at the cost of creating either unnecessary coupling between the Instead of fixing this issue, we can explicitly recommend fetching the last insert ID immediately after Inconsistency in what the last inserted ID looks likeYes, there is an inconsistency between what an empty ID looks like (all kinds of empty values) and the non-empty ones (integer or a string). But it's natural for a weakly-typed language like PHP.
Depite the fact that IDs are mostly integers in the DB internals, we cannot represent them as such due to potential overflow issues. At the same time, declaring them strings on the API level is misleading in the way that they are not just strings (e.g. Technically, we can re-iterate on the type consistency later, after #3005 is resolved but besides being theoretically correct, I don't see a practical value in it. |
Reopened #2765 from the personal repo (replaces #2648).
TODO
string, not anint? Because on a 32-bit system, a numeric ID will be represented as a string by the driver (e.g.mysqli). In a general case, an extremely large ID will not fit the int range anyways.NULL? Because some drivers return0when there's no ID. Besides that, for the caller, there's no difference between0andNULL, they both mean "none".