-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
adds BaseResult::getNumRows(). adds getNumRows to various DBMS Result classes #4049
adds BaseResult::getNumRows(). adds getNumRows to various DBMS Result classes #4049
Conversation
… classes. tweaks MockResult class. Adds documentation. This is my signed initial commit.
https://github.com/codeigniter4/CodeIgniter4/pull/4049/checks?check_run_id=1632239856
SQLite3 test fails, because it throws an exception. if ($this->db->DBDriver === 'SQLite3')
{
$this->markTestSkipped('SQLite3 does not support comments on tables or columns.');
} It is from |
@kenjis THANK YOU for the very helpful information. Does that mean I should close this PR and create yet another? OR should I make that one little change and push to that branch again? Will this re-trigger the tests? |
No.
Yes.
Yes. |
@sneakyimp https://github.com/codeigniter4/CodeIgniter4/pull/4049/checks?check_run_id=1632239808
Run
|
system/Database/SQLite3/Result.php
Outdated
*/ | ||
public function getNumRows() : int | ||
{ | ||
throw new \Exception('SQLite3Result does not support a numRows method. Use Builder->countAllResults() instead.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use CodeIgniter\Database\Exceptions\DatabaseException
instead of Exception
.
I think you should use/update a public variable I don't like the idea of throwing an exception for SQLite3. We probably should "simulate" this functionality when it's not available. That being said, we can add the "global" method |
I like @michalsn 's suggestion - we also cannot add a method to the |
Actually even better would be to do what Michal said but deprecate |
My first thought is that this approach would require more changes to other CodeIgniter classes. In particular, we would need to make sure we set this Put another way, I think these DBMS modules are already storing some value in memory and that these various num_rows functions already function as efficient getters for some value stored in memory. Consider taking a look at the source code for mysqli_num_rows. It's a bit of a chore to chase down the macro definitions, but it leads to what looks like a struct reference.
The comments for the SQLite3Result class suggest this code to obtain a numRows count, but this requires the code to loop thru every record of the result set, and would alter the state of the query/result object that has been returned: $nrows = 0;
$result->reset();
while ($result->fetchArray())
$nrows++;
$result->reset();
return $nrows; Note how it resets the result twice. The looping might be a hint of the 'performance and memory issues' by @lonnieezell in #109. Perhaps we could alter this code to take a snapshot of the current result index and then do a dataSeek to that same index after this function runs. |
In the interest of simulating a numRows function for SQLite3, I took a peek at the source code for the SQLite3\Result::dataSeek source and see that it throws a DatabaseException if you try to use that function with any value of $n other than zero: public function dataSeek(int $n = 0)
{
if ($n !== 0)
{
throw new DatabaseException('SQLite3 doesn\'t support seeking to other offset.');
}
return $this->resultID->reset(); // @phpstan-ignore-line
} My original thinking was that, because SQLite3Result has no numRows function, throwing an exception which suggests the use of builder->countAllResults() would be the most helpful information to developers using SQLite3. The downside of this approach is that any CI4 project using my getNumRows function would break if they switched the driver to SQLite3. This seems very unlikely to happen in practice, but who knows? I think the best approach would be to implement the loop-count approach above and, if possible, emit a warning. However, CodeIgniter throws ErrorException by default when you emit even the tiniest of errors: trigger_error("using getNumRows with SQLite3 resets the result array and is discouraged.", E_USER_NOTICE); Is there some recommended best practice for warning developers? Perhaps we can write a log or something? |
I don't really understand how relevant is the fact the database module takes the OOP approach or procedural. I believe we can easily make The idea behind storing this value separately in the For SQLite3 we can take a more "brutal" approach and just force to count the result array - just an idea. Personally, I just can't imagine not supporting such a method for all database drivers. SQLite3 is commonly used for testing environments (when the project isn't too complex) and the lack of this method would be a big pain for developers. |
The point I was expressing (rather poorly) is that the underlying database drivers that support any numRows method are already maintaining such a variable in memory and the various driver functions (mysql_num_rows, sqlsrv_num_rows, and pg_num_rows) are already acting efficiently as a getter method. I don't think adding a private/protected numRows property to the CodeIgniter classes gains any appreciable functionality. On the contrary, maintaining a property $numRows in our PHP classes just requires more code, more memory, and more CPU cycles than just using the underlying driver functions.
I was trying to imagine how we might use a 'lazy loading' approach for this numRows property and I suppose we could set it to I still don't see any good reason for us to maintain a numRows private/protected property except for the SQLite3 situation, where the getNumRows method is somewhat costly. In every other case, I believe the underlying DBMS is performing an efficient memory read operation on some record count integer stored in memory.
The code I've supplied in this pull request will currently throw an exception if you try to call public function foo() {
$db = \Config\Database::connect("tests");
$query = $db->query("select * from db_job");
$query->freeResult();
var_dump("Rows: " . $query->getNumRows());
} This throws an ErrorException:
I posted some example code above to do exactly this and tried to point out the tradeoffs. I'd appreciate any thoughts you have. Also please note the Exception SQLite3 throws in the dataSeek function.
I agree that a getNumRows function is efficient and useful. I'm migrating a CI3 project that uses this function 100 times. I would point out that CI4 already lacks this function (builder->countAllResults is a different beast). Should we adopt this pull request, it should have no impact on any other db functionality as it is currently written. The only impact my pull request would have on existing projects is that instances of a BaseResult or db\Result object will consume slightly more memory due to the presence of a single additional method (which is almost trivial). No one is under any obligation to use such a function, regardless of which DB driver they use. The use of Result->getNumRows is entirely optional. |
Storing I will adjust your example a little public function foo() {
$db = \Config\Database::connect("tests");
$query = $db->query("select * from db_job");
var_dump("Rows: " . $query->getNumRows());
$query->freeResult();
var_dump("Rows: " . $query->getNumRows());
} From my perspective, the second call shouldn't return an error. Simply because the number of rows for this query did not change. I find the proposed behavior as strange from a logical point of view. Keep in mind that users usually don't care what is going on under the hood - they want stable api between drivers. I understand your point of view, but we try to provide consistent solutions whenever it's possible. Okay, I think you already know my preference for handling this feature. Anyway, if other members will agree with your idea then it will be merged. |
…od. Moved BaseResult::numRows to SQLite3\Result because it is not used in the other DBMS options. Tweaked BaseResult class to use getNumRows method instead where appropriate. Cleaned up exception throwing and stan/rector complaints. Also some tweaks made by PHPCBF or other automated scripts.
@michalsn I really appreciate your input, and hope that you don't find my latest commit off-putting.
In the current CI4 develop branch, the public property BaseResult::numRows is never set anywhere. One's machine will allocate memory for this value every time such a result is instantiated but the code doesn't currently do anything but check if Also, the num_rows functions provided by the underlying database drivers may return different values depending on whether one is buffering results. From the documentation:
This behavior apparently depends on whether you use
If this code were to output
Since the numRows property is never set to anything in the current develop branch, I took the liberty of moving it to the SQLite3\Result class and made it protected there. I changed the BaseResult class to refer instead to the new getNumRows() method in its methods that were checking numRows===0 (which would always be false). This methods are:
I am aware that some developers may have tried to reference the Result::numRows property, but contend that this would have been a mistake because it would never be anything but null unless explicitly set by the developer themselves. I've modified the pull request and hope you folks might kindly take a look. I see that the SQLSRV tests are failing for some reason. I'll look into that. |
OK so that's weird the SQLSRV checks failed this time. They ran just fine two days ago. It looks from the errors like sqlsrv_num_rows is returning public function getNumRows() : int
{
$retval = sqlsrv_num_rows($this->resultID);
if ($retval === false)
{
throw new DatabaseException('Error retrieving row count');
}
return intval($retval);
} This latest function is nearly identical to the one that ran previously except for the type of exception thrown and the if/else statement: public function getNumRows() : int
{
$retval = sqlsrv_num_rows($this->resultID);
if ($retval === false)
{
throw new \Exception('Error retrieving row count');
}
else
{
return intval($retval);
}
} Might someone have changed the type of cursor used for these automated tests? The documentation on sqlsrv_num_rows says:
Looking at the option value provided for
However, |
Digging deeper into the SQLSRV tests, it looks like my changeup of This is tricky to sort out because I don't have access to any MSSQL server. Looking at the code in getResultArray(), it looks like the resultID is neither boolean nor empty but for some reason it returns false when fed to sqlsrv_num_rows. Not sure where the best place is to fix this problem, but leaning toward BaseResult. |
For the As you mentioned there are different flags for |
As for |
I agree there's a problem, but it's my feeling that folks shouldn't be calling getResultArray for queries that just return boolean values. It seems extremely odd to call mysql_fetch_assoc on the results of a DROP TABLE query, don't you think? As I mentioned before, mysqli_query only returns records sets for SELECT, SHOW, DESCRIBE or EXPLAIN queries. For everything else, the docs say it returns true or false. |
You all have moved beyond my database comfort zone, but I'm grateful for the work you're doing. |
Thanks for the encouragement, @MGatner. I feel like a bit of a troublemaker, but want to help make CI4 better. I have reported an issue, #4059, with the DbUtilsTest that is causing difficulty for this pull request. I hope it might be informative and would appreciate anyone's input. |
I haven't checked what is going on there - I just pointed out where is the problem, since you were wondering why your tests are failing. If other drivers work the same way I guess this is something that should be fixed. |
This discussion is largely over my head, but for what it is worth, our Codeigniter 3 project code frequently checks num_rows. Keeping this intact would be nice when we migrate to 4. |
@michalsn As previously discussed, I have tried implementing the getNumRows functions as closely to the CI3 code as possible. The significant differences are that
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @sneakyimp. I made some more comments - some of them are related to code style, but I understand that it's because of problems with tools.
You may try to remove the pre-commit hook and then add it again? I'm not really sure what is going on since it's not really my field of knowledge. I checked it locally and code sniffer actually fixed it correctly. Maybe we should try to call @samsonasik for help.
@sneakyimp #4049 (comment) You could commit by |
😮 @kenjis You've just saved me so much heartache. I've been editing the pre-commit hook every time 🤦♂️ |
Thank you, @kenjis. I had managed to figure that out on my own. That said, I definitely did not set up these git hooks on my own. If we have two different automatic code editors that can't agree on each other, that seems bad -- a lot of spurious commits and a lot of failed tests when you finally do commit to github. Maybe someone (more experienced with these auto tests than I am) should make sure that Rector and Code Sniffer don't have some conflict? |
…ved getNumRows method declaration from ResultInterface per michalsn
OK I believe I've made all the change requests from @michalsn and @samsonasik. Please review and let me know your feelings. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the last round from me.
Just some tiny things, but I'm requesting these changes because it's "now or never" 😄 Probably no one would go back to it after a merge.
I have made the changes requested by @michalsn and am baffled by the test failures I'm seeing. These appear to be caused by files that I have not touched and which have passed the tests on previous commits. What might be causing these failures? |
Why
https://github.com/codeigniter4/CodeIgniter4/pull/4049/checks?check_run_id=1684018456 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @sneakyimp!
PHPStan error is already fixed in a different PR.
Predis error is probably related to the slow tests. It happens only once in a while.
I appreciate everyone's guidance and openness to my ideas.
Do we need to re-trigger the tests somehow? It's so disappointing when any fail -- and so gratifying when they all succeed. |
When we merge the PR that fixes the errors, I will run tests again (unless we merge this earlier). |
@sneakyimp Sorry, I think I misled you - you need to rebase to make tests working properly. If you're not familiar with rebasing, you can follow this article: https://samsonasik.wordpress.com/2015/09/16/practical-git-4-rebasing-conflicted-task-branch-against-primary-branch/ |
I started trying the various commands in that article and when I ran EDIT: I added the CI4 repo as a remote to my local git project and pulled the latest changes to my own develop branch and also to my num-rows-signed branch and then merged those upstream changes into my repo. I then pushed to my fork repo and this has brought my fork current with the CI4 repo and the tests are running again. |
@sneakyimp
|
@kenjis must I still do this? I performed the first two steps there and merged things and pushed and my commit here has passed all tests. |
@sneakyimp I don't know. But if you rebase it, your commit graph will be clean. |
Okay, no worries, we have an option to squash before the merge. |
Thanks so much for all your work! |
Added unit tests/system/Database/Live/GetNumRowsTest.php for this newly added method. Added an entry in the user docs for getNumRows method. Tweaks MockResult to fix phpstan problem and class definition conflict. This is my signed initial commit.
Description
Per Lonnie Ezell's message in #109, I have attempted my first CodeIgniter fork/branch/pull request here. It adds a getNumRows method to the ResultInterface and the various DBMS classes that extend it. This pull request supercedes a couple of messy ones (for which I apologize).
Checklist: