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

[11.x] PDO modes on query builder #50307

Closed
wants to merge 10 commits into from
Closed

Conversation

bert-w
Copy link
Contributor

@bert-w bert-w commented Feb 28, 2024

PDO modes

This PR adds the ability for developers to customize the underlying PDO mode for a query. Particularly, this is beneficial for certain queries like pluck() since they can be retrieved more efficiently using a specific PDO fetch mode.

The gist of this PR is the addition of the $query->mode() function which can be called with a set of predefined modes that changes PDO settings just before executing/fetching the $statement in the Connection class.

Features

Improved $query->pluck()

Faster $query->pluck() retrieval (see benchmarks below) by utilizing the PDO::FETCH_KEY_PAIR mode (now built into the pluck function). The end-result of the pluck function remains unchanged and the original pluck behavior has already been added to the tests in #48657.

Additionally, raw DB statements can now be added for the $key parameter as well.

// Improved speed + simplification of query `pluck()` + ability to use raw expressions for both key and column:
User::query()->pluck('name', 'group');
User::query()->pluck(DB::raw('UPPER(name)', DB::raw('UPPER(group)'));

Added $query->mode(DB::mode()->keyed())

The "keyed" mode consumes the first column of the result and uses it as the key for the returned collection. It can be set using $query->mode(DB::mode()->keyed()) on a query builder. Rows with duplicate keys will overwrite the previous entry, just as the $collection->keyBy('...') function.

This provides a nice optimization (see benchmarks below) for cases where you previously would have called User::query()->get()->keyBy('...') (retrieving a query, after which the collection is looped and keyed using PHP). Now, the result is keyed as it comes back from PDO which saves a complete processing loop.

// New syntax to key a result instantly as it comes from PDO,
// without needing to call Collection `keyBy()`:
User::query()->mode(DB::mode()->keyed())->get(['group', 'users.*']);

Added $query->mode(DB::mode()->buffered(false))

The "buffered" mode changes the buffer setting (only applicable to MySQL) which may provide memory optimizations for processing large datasets using a cursor.

// (un)buffered mode (MySQL) for use with (very) big datasets in conjunction
// with cursors (unbuffered can reduce memory in these cases):
User::query()->mode(DB::mode()->buffered(false))->cursor();

Added $query->mode(DB::mode()->scrollableCursor($nth))

The "scrollableCursor" mode changes how the cursor runs and skips through the result set. For instance, providing the parameter $nth = 2 takes only every second result from the cursor. This functionality is only available on Postgres and MS SQL.

// Scrollable cursor mode (Postgres+MSSQL), which fetches every nth item lazily:
User::query()->mode(DB::mode()->scrollableCursor($nth))->cursor();

Notable changes

  • Created src/Illuminate/Database/PDO/Mode.php which has common PDO modes for the developer to use.
  • Created src/Illuminate/Database/PDO/Statement.php which is a wrapper around PDO Statement. (it is purposively not extended since that may introduce difficulties when providing support for different PHP versions).
  • Signatures changes in src/Illuminate/Database/ConnectionInterface.php namely the addition of the new $mode:
    • public function select($query, $bindings = [], $useReadPdo = true, Mode $mode = null);
    • public function cursor($query, $bindings = [], $useReadPdo = true, Mode $mode = null);
  • Changed src/Illuminate/Database/Query/Builder.php:
    • Removed protected function onceWithColumns() and used $this->clone() in those cases where it was used, to simplify and flattten the function.
    • Changed public function pluck() to use the new mode
    • Removed the protected functions stripTableForPluck(), pluckFromObjectColumn() and pluckFromArrayColumn() since those functions are no longer necessary for pluck(). The pluck result is now always returned as ['key' => 'value'] (no longer in object format), so no further parsing is needed.
  • Changed some tests to fix the select() function mock, since a new (optional) parameter was added which was no longer recognized by mockery (even though it is an optional parameter).

Benchmarks

Benchmark (gist) of 1000 iterations on various amounts of rows:

Operation (rows) Branch (ms) Master (ms) Percentage Difference (%)
Pluck (10) 0.263409443 0.275019577 -4.400
Pluck (100) 0.288879615 0.304758434 -5.491
Pluck (1000) 0.552295596 0.64445222 -14.358
Pluck (10000) 2.981094529 4.101525242 -27.545
Keyed (10) 0.27759889 0.27788213 -0.102
Keyed (100) 0.3193552 0.346400055 -8.478
Keyed (1000) 0.658895505 0.882822845 -25.433
Keyed (10000) 4.333648892 6.390425972 -47.436

Copy link

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.

@bert-w bert-w marked this pull request as ready for review March 1, 2024 14:16
@taylorotwell
Copy link
Member

Thanks for your pull request to Laravel!

Unfortunately, I'm going to delay merging this code for now. To preserve our ability to adequately maintain the framework, we need to be very careful regarding the amount of code we include.

If possible, please consider releasing your code as a package so that the community can still take advantage of your contributions!

If you feel absolutely certain that this code corrects a bug in the framework, please "@" mention me in a follow-up comment with further explanation so that GitHub will send me a notification of your response.

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.

None yet

2 participants