-
Notifications
You must be signed in to change notification settings - Fork 11.3k
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
[12.x] Query builder PDO fetch modes #54443
Conversation
Thanks for submitting a PR! Note that draft PR's are not reviewed. If you would like a review, please mark your pull request as ready for review in the GitHub user interface. Pull requests that are abandoned in draft may be closed due to inactivity. |
Would it make sense to be able to pass this to |
Yes you can already do that since this method is automatically forwarded to the underlying User::query()->select(['id','users.*'])->fetchUsing(PDO::FETCH_UNIQUE)->get(); Would give you a collection where the keys are Note that in this case, the |
I actually meant more like this: User::query(PDO::FETCH_UNIQUE)->select(['id','users.*'])->get(); |
I like this approach but it feels a little bit weird 🤔 |
@flavio-schoute Because we're passing a |
@bert-w can you fix the conflicts on this? |
@taylorotwell conflicts are now resolved. There appear to be some SSL/TLS errors though. I removed/reverted a test that was added in #54396 since it no longer applies in this setup. It tested the case where if you do |
@bert-w is it possible to revert all changes to |
@taylorotwell I have reverted most changes to the query builder except the Bug description: User::query()->select(['id'])->pluck('name', 'id');
// Error: Undefined property: stdClass::$name The query above doesn't work because The same thing happens at // \Illuminate\Database\Query\Builder.php::3099
$this->columns ??= Arr::wrap($columns); which functions exactly the same as without the |
Sorry for the late question. Is this supposed to be public? It has a setter and it is used only in the query builder. /**
* The custom arguments for the PDOStatement::fetchAll / fetch functions.
*
* @var array
*/
public array $fetchUsing = []; |
@macropay-solutions I followed the rest of the properties in the class (of which most are public). There are no getters for these properties so having them public is the only way to access them in your code (regardless of if you should). I guess another good question you could ask is: why is there an |
Thank you. We will overwrite it as public then to keep support for laravel/lumen >=8. No additional questions from our side. PS. it is a nice feature when you know what you are doing. |
While this might make sense stylistically, it might actually point out flaws in the design which you might have added to, but if it would have been a larger issue, Taylor sure would have said so :) |
@taylorotwell will this remain public or not? /**
* The custom arguments for the PDOStatement::fetchAll / fetch functions.
*
* @var array
*/
public array $fetchUsing = []; UPDATE Taking into account the rollback, this question is obsolete. We will keep the implementation in our lib anyway. |
This reverts commit ca7ca2c.
@bert-w Taking into account that this MR was reverted, can you please confirm that onceWithColumns function will work with (only) these changes: public array $fetchUsing = [];
public function fetchUsing(...$fetchUsing): static
{
$this->fetchUsing = $fetchUsing;
return $this;
}
...
$statement->fetchAll(...$fetchUsing);
... |
@macropay-solutions it's merged in another PR with fixes |
@decadence can you pls link that PR? the code is not in 12.x branch
|
@macropay-solutions #54734 |
@macropay-solutions this PR was reverted because of a bug, but a day later I made a one-line fix and it was merged in again. This feature was probably too close to the 12.x release date, so that might be the reason that it now remains on master (12.x was branched off just a few commits earlier so this was not included). I guess it will become part of 13.x in the future since this PR brings some minor breaking changes. |
@bert-w |
@macropay-solutions I have no clue what exactly your lib does, however |
@bert-w rephrasing :) In laravel 8, 9, 10, 11, 12, (disregarding our lib) imagine we hardcode in vendor a fetch mode in that function call
Will it work in the sense that the function onceWithColumns will be able to handle the result? Because if not, we have to throw an exception when the function fetchUsing is used to prevent devs using it until they use the lavarel version in which it will be published. |
@macropay-solutions it will pass through
Stuff may start to go wrong depending on what you do with your query afterwards. I suggest you just take a look at the code because you could've gotten your answer 2 weeks ago; the changes aren't that complex really :) Honestly if you don't know whether stuff will work in your application for version x under feature y, you obviously need to write some tests. |
@bert-w Our tests could not cover all situations, that is why we asked. |
Hi, this is not the first time I've requested this idea but I would like you to reconsider the following addition for Laravel 12.x since I think it opens up many many performance improvements. This PR has simplified the premise a little bit by only relying on a single new
$query->fetchUsing()->...
function.PDO fetch modes allow developers to control the way how database statements are retrieved/used in PHP. Currently in Laravel, the default mode is
PDO::FETCH_OBJ
which returns a php object for every row in the result set. By making this available as a query modifier, certain optimizations can be achieved because the data format is defined through PDO.There are various fetch modes that can offer greater efficiency of the code, e.g. retrieving a dictionary of 2 columns with
PDO::FETCH_KEY_PAIR
(which is exactly the query builderpluck()
function) or keying a resultset instantly by some other column (PDO::FETCH_UNIQUE
). By using specific PDO modes, a lot of processing that was traditionally done using Laravel's Collection methods can now be done using a simple query modifier.TLDR
This PR adds the following:
fetchUsing(...$args)
function for a new way to modify the format of the query result.pluck()
to use this new PDO mode to remove unneeded code and speed up the execution.array $fetchUsing = []
argument (Breaking Change -> Laravel 12).onceWithColumns
because it behaves weirdly (it only actually uses your arguments if no columns have been assigned). 2 internal references have been simplified to not use this function anymore.Usage examples
Key-value pairs (
pluck
)By selecting only two columns and using
PDO::FETCH_KEY_PAIR
, the result is automatically returned as a dictionary, requiring no further processing:Or as a list of values:
Both of the syntaxes above have been worked into the
pluck()
method, so you can just callUser::query()->pluck()
and benefit from both the key-value and list-value optimizations.Key a collection by a given column (
keyBy
)When using
PDO::FETCH_UNIQUE
, the first column in the select-statement is consumed as the key for the output array, so it functions similar to$collection->keyBy('id')
but instead it is returned directly from PDO (so no more$collection->keyBy()
required):This will come in handy for various algorithms, since the array keys now have a meaning and can be used to retrieve data.
Group x by y (
mapToGroups
)A bit of an odd one, but say you have a column
role
which contains either'admin','moderator','user'
and you want to retrieve usernames for each of the groups. Traditionally, you'd have to loop over the array in PHP and build these groups yourself using for instance$collection->mapToGroups(...)
. There is however a much faster way:Cursors
All of the above will also be available for the
$query->cursor()
function so you can lazily process your data.Benchmarks
Test file: https://gist.github.com/bert-w/3f716af1741c380a8517ea6a7e757854
If you want to run this yourself, note that you have to re-add the old
pluck()
function insidesrc\Illuminate\Database\Query\Builder.php
and rename it topluckOld()
, including the functionsonceWithColumns
,stripTableForPluck
,pluckFromObjectColumn
,pluckFromArrayColumn
(they are not necessary anymore after this PR).┌─────────────┬────────┬───────────┬────────────┬─────────────┐ │ Test │ Branch │ # Records │ Time (ms) │ Improvement │ ├─────────────┼────────┼───────────┼────────────┼─────────────┤ │ keyBy │ branch │ 5 │ 0.1951882 │ 3.6% │ │ keyBy │ master │ 5 │ 0.2024828 │ │ │ keyBy │ branch │ 50 │ 0.2679888 │ 4.25% │ │ keyBy │ master │ 50 │ 0.279884 │ │ │ keyBy │ branch │ 500 │ 1.2666686 │ 8.94% │ │ keyBy │ master │ 500 │ 1.3910648 │ │ │ keyBy │ branch │ 5000 │ 9.3656038 │ 14.9% │ │ keyBy │ master │ 5000 │ 11.0052914 │ │ │ keyBy │ branch │ 25000 │ 47.1913486 │ 15.03% │ │ keyBy │ master │ 25000 │ 55.5400322 │ │ │ mapToGroups │ branch │ 5 │ 0.1496776 │ 4.99% │ │ mapToGroups │ master │ 5 │ 0.1575438 │ │ │ mapToGroups │ branch │ 50 │ 0.195247 │ 10.72% │ │ mapToGroups │ master │ 50 │ 0.218697 │ │ │ mapToGroups │ branch │ 500 │ 0.5607566 │ 22.3% │ │ mapToGroups │ master │ 500 │ 0.721686 │ │ │ mapToGroups │ branch │ 5000 │ 3.4245092 │ 31.18% │ │ mapToGroups │ master │ 5000 │ 4.9759744 │ │ │ mapToGroups │ branch │ 25000 │ 15.2814816 │ 39.06% │ │ mapToGroups │ master │ 25000 │ 25.0745594 │ │ │ pluck │ branch │ 5 │ 0.1543958 │ 4.79% │ │ pluck │ master │ 5 │ 0.1621634 │ │ │ pluck │ branch │ 50 │ 0.1914712 │ 3.45% │ │ pluck │ master │ 50 │ 0.1983118 │ │ │ pluck │ branch │ 500 │ 0.531743 │ 9.38% │ │ pluck │ master │ 500 │ 0.586767 │ │ │ pluck │ branch │ 5000 │ 3.2316182 │ 16.54% │ │ pluck │ master │ 5000 │ 3.8720818 │ │ │ pluck │ branch │ 25000 │ 13.832984 │ 26.2% │ │ pluck │ master │ 25000 │ 18.7447848 │ │ └─────────────┴────────┴───────────┴────────────┴─────────────┘
Conclusion
The whole reason for this PR is to run code as efficiently as possible. The more we can ask from PDO to build the correct data structure, the less we have to rely on Laravel Collection methods to reprocess our data.
All of the modes mentioned above can help tremendously when building efficient algorithms to process data from your database. Please look carefully at this PR and communicate any issues you might see with this implementation.
Some other resouces to have a look at if you want to know more: