-
Notifications
You must be signed in to change notification settings - Fork 11.1k
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
[5.2] Allows developers to traverse their query result via a cursor #13030
Conversation
Efficiently process thousands of records, uses least amount of memory, hits the database once with single query, allows you to use a ‘cursor’ to process the result set via a callback/closure.
If this is to go ahead, I propose we name the function either Fetch is too generic (and already in-use). |
Set Fetch mode Switched to using generators
Changed the name to traverse() as it more intuitively describes what is being done. |
Please squash to one commit. Also, it doesn't matter if StyleCI is failing. We have it set to auto-merge the fixes whenever we merge PRs. Please see our contribution guide. It does matter that travis is failing though. |
// developer take care of everything within the callback, which allows us to | ||
// keep the memory low for spinning through large result sets for working. | ||
|
||
$continue = (yield $row); |
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.
Why do we need to assign here? Should this not just be:
if ($row === false) {
return;
}
yield $row;
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.
First thought was allowing for a response to be sent back, but for this use case you are correct it is not needed.
Efficiently process thousands of records, uses least amount of memory, hits the database once with single query, allows you to use a ‘cursor’ to traverse the result set.
@GrahamCampbell You don't have to squash your commits in a PR, you can just squash them while merging. See https://github.com/blog/2141-squash-your-commits |
Haven't thorougly reviewed the code, but if this allows for more efficient queries on large datasets, big +1 on the idea. There should be tests for such crucial and low-level operations. |
Don't like adding new api for something that seems like it should be written the right way. Rather than adding new api to safely iterate through a large db result set, is it possible to change the base db collection classes to fetch one row at at time? |
if there is to be new api recommend the name "cursor()" as in: foreach( Model::where('foo', 'bar')->cursor() as $model) {
} Would align with |
Now returns Eloquent model |
As I can see, the only drawback is that it's impossible to eager load results. That said, I think this is a feature that should be added, and a small note made about the limitations. |
👍 being able to query via a cursor would be very helpful for large data dumps/aggregation jobs |
Cursor is the right name for this, please don't change it. |
Could it be called "iterate"? Or something similar to " iterator"? |
//Hydrate and yield an Eloquent Model | ||
$model = $this->model->newFromBuilder($row); | ||
|
||
yield $model; |
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.
Finally, the first php yield
keyword in laravel! 😀👍
As @jbrooksuk said, please keep it cursor. |
No. |
One addition I'd recommend is adding a call to "To ensure that the application will work with all database drivers, the author inserts a call to PDOStatement::closeCursor() on $stmt before executing the $otherStmt PDOStatement object." |
How would eager loading work with this? |
@JosephSilber it wouldn't as far as I can see, as you can't retrieve any
|
It kinda breaks the way Eloquent works, so I think this should only exist on the base query builder. If you really need Eloquent objects, you can hydrate them yourself. foreach (User::where('foo', 'bar')->toBase()->cursor() as $row) {
$user = (new User)->newFromBuilder($row);
// Do something complex with each $user
} This would require a matching method on the Eloquent builder that throws an error if used directly. BTW, |
Agreed, it should be on the QB I guess
|
I like it as part of eloquent; it seems to me that it being part of eloquent takes care of the most common use. I feel like @JosephSilber 's example would be the majority of use cases. |
Thanks for the feedback and comments. What changes, if any, are needed for this to be accepted? |
Just pulling this down and running
Is this supposed to be working? If I can't even do that how do I know the rest of this was even tested manually? |
Use case was for eloquent only.
I will take a look at that and fix it. |
It doesn't work like that either... Same error. Did you actually test that this was working on your machine? |
I'm fixing it all. Don't worry about it. |
Fixed all the problems with this. DB::cursor('sql') now returns a generator itself. The query and Eloquent versions use that. The Eloquent version returns another generator. |
:D |
@dark-panda mentioned using PDOStatement::closeCursor() - #13030 (comment) Should/Could this be included as well? |
I’m not sure. Maybe.
|
Thought about doing something like this:
But this breaks HHVM tests, as it is not supported. |
Is there documentation for this? |
Doesn’t look like it has been added yet.
|
pinging @kharysharpe, otherwise no doubt there will be little hands to take care of this. |
I keep a list of all things needing documentation so I can do it for 5.3 release :)
|
@vlakoff I will make an attempt at documentation tonight. |
Just to bring it a bit more visibility, I've added a code comment concerning fetch mode. |
Refs #14052, which fixes support for |
+1, but generate() makes more sense to me than cursor(), as it more clearly describes what is happening under the hood at the PHP level and maintains a level of abstraction with the concept of a true DB cursor, which this isn't AFAIK. |
Ran into an issue with attempting to use chunk wherein the underlining database would change and therefore the data would no longer be as initially requested. It is also generated a query for every chuck it returns.
Traditionally in php we would do something like below, but I found no Eloquent equivalent.
Usage:
Update: Using generator instead of callback
Update: Using Eloquent model and changed name to cursor