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

[12.x] Query builder PDO fetch modes #54443

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
Draft

Conversation

bert-w
Copy link
Contributor

@bert-w bert-w commented Feb 2, 2025

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->fetchArgs()->... 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.

There are however various other 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 like the query builder pluck() function) or keying a resultset instantly by some other column (PDO::FETCH_UNIQUE). Making these options available to the query builder will mean faster code since 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:

  • Added Query Builder fetchArgs(...$args) function for a new way to modify the format of the query result.
  • Simplified Query Builder pluck() to use this new PDO mode to remove unneeded code and speed up the execution.
  • Changed several Connection methods to accept an optional array $fetchArgs = [] argument (possibly Breaking Change).
  • Removed protected Query Builder function 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

Return key-value pairs

By selecting only two columns and using PDO::FETCH_KEY_PAIR, the result is automatically returned as a dictionary, requiring no further processing:

User::query()->select(['id', 'username'])->fetchArgs(\PDO::FETCH_KEY_PAIR)->get()->toArray();
array:3 [
  1 => "ape"
  2 => "bear"
  3 => "snake"
]

This PR rewrites the pluck() method to use this idea, and because of that a slew of internal parsing methods have now been removed since the resultset is correct from the get-go.

Benchmark (click)
Version Records Time (ms) % Improvement (vs Master)
Branch 50 0.1767342 9.0%
Branch 500 0.5084815 15.0%
Branch 5000 3.1547129 13.6%
Master 50 0.1942677 -
Master 500 0.598063 -
Master 5000 3.6528093 -

Key a collection by a given column

The first column in the select-statement is consumed as the key for the output array, so it essentially functions as a $collection->keyBy('id') but instead it is returned directly from PDO (so no more $collection->keyBy() required):

User::query()->select(['id', 'users.*'])->fetchArgs(\PDO::FETCH_UNIQUE)->get()->toArray();
array:3 [
  1 => array:4 [
    "id" => 1
    "username" => "ape"
    "created_at" => "2025-02-02T17:46:22.000000Z"
    "updated_at" => "2025-02-02T17:46:22.000000Z"
  ]
  2 => array:4 [
    "id" => 2
    "username" => "bear"
    "created_at" => "2025-02-02T17:46:23.000000Z"
    "updated_at" => "2025-02-02T17:46:23.000000Z"
  ]
  3 => array:4 [
    "id" => 3
    "username" => "snake"
    "created_at" => "2025-02-02T17:46:23.000000Z"
    "updated_at" => "2025-02-02T17:46:23.000000Z"
  ]
]

This will come in handy when some algorithm needs to retrieve users by key, since that is an O(1) (efficient) operation.

Fetch single column as list

User::query()->toBase()->select(['username'])->fetchArgs(\PDO::FETCH_COLUMN)->get()->toArray();
array:3 [
  0 => "ape"
  1 => "bear"
  2 => "snake"
]

Group username by role

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. There is however a much easier way:

User::query()->toBase()->select(['role', 'username'])->fetchArgs(\PDO::FETCH_GROUP | \PDO::FETCH_COLUMN)->get()->toArray();
array:3 [
  "user" => array:3 [
    0 => "Jonas"
    1 => "Jonas"
    2 => "John"
  ]
  "moderator" => array:1 [
    0 => "Jimmy"
  ]
  "admin" => array:1 [
    0 => "Jake"
  ]
]

Cursors

All of the above will also be available for the $query->cursor() function so you can lazily process your data.

Conclusion

Keep in mind that 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. @taylorotwell I urge you to 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:

Copy link

github-actions bot commented Feb 2, 2025

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant