Skip to content

[10.x] Optimize eager loading when no keys to be loaded #43724

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

Merged
merged 4 commits into from
Sep 15, 2022

Conversation

garygreen
Copy link
Contributor

@garygreen garygreen commented Aug 16, 2022

Sending this again. @taylorotwell please could you provide suggestions on what I can do to get this merged?

Currently when eager loading relations it causes many impossible wheres to be executed, slowing down application load time. The reason for that is mentioned in the original PR - Laravel will add an impossible where 0 = 1 which would never return a result for an eagerly loaded relation. This PR fixes those eager loading scenarios, which are very common with NULL column values.

If you could provide alternative suggestions on how to get this merged that would be great.

image

@taylorotwell
Copy link
Member

I'm curious if there is a reason this was sent to 10.x instead of 9.x?

@garygreen
Copy link
Contributor Author

garygreen commented Aug 16, 2022

The reason I sent it to 10.x is because it potentially poses a behavioural change, but only for a strange edge case scenario when adding constraints to the eager load:

$user->load(['picture' => function($q) {
	$q->orWhere('id', 'some other id')->orWhere('id', 'another id');
}]);

In 9.x this currently results in this query:

image

Which could return results, but with this PR it wouldn't return any results because the model didn't have any loadable relation keys. I would say the latter is expected, and you could say this is actually an unintended bug in Laravel at the moment. Personally I would expect a query more like the below where it should restrict loading of model to that particular set of keys being loaded - i.e. the constraints is ADDITIONAL as oppose to a complete override:

select * from `pictures` where 0 = 1 and (`id` = 'some other id' or `id` = 'another id')

To be on the safe side it was sent to 10.x even though this shouldn't affect anything because when the eagerly loaded models attempt to be set it won't match to the dictionary of relation keys, as is the case with BelongsTo

@Tofandel
Copy link
Contributor

Tofandel commented Aug 22, 2022

What you describe is a bug, the query should definitely not load entities that don't pertain to that model (especially if the relation is marked as null in the first place), it could in fact be a security issue as that could load data from other users unexpectedly

Normally closures are added as subQueries to prevent wrong operator priority, this doesn't seem to be the case here

It should be sent to 9.X or 8.X imo

@garygreen
Copy link
Contributor Author

What you describe is a bug [...] It should be sent to 9.X or 8.X imo

Agreed, I think it's a bug as well although this PR doesn't really intend to fix that. It technically does fix it for eager loading when all relations are null but that's just because it doesn't need to hit the database at all, so doesn't even attempt to build a query. For any others it would still allow a query like this:

select * from `pictures` where in (15, 88, 99) or `id` = 'some other id' or `id` = 'another id'

...because there are loadable model keys, so it go onto hitting the database constructing the query as above. Solving that should be a separate PR which wraps those additional eager constraints in a parentheses group.

@Tofandel
Copy link
Contributor

I see, I thought you fixed it for all cases, then yes there should be a new PR to fix this issue, but then this PR is still not breaking any expected behavior

Comment on lines 412 to 414
if ($modelKeys === []) {
$this->eagerImplicitlyEmpty = true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional that once this property is true that it'll always be true?

Suggested change
if ($modelKeys === []) {
$this->eagerImplicitlyEmpty = true;
}
$this->eagerImplicitlyEmpty = $modelKeys === [];

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well if the eager is loaded again it'll make calls to the relation methods addEagerConstraints which subsequently call whereInEager which could change the eagerImplicitlyEmpty property because it will know if there are any loadable keys. So once the eager constraints are added, it will set that property.

@driesvints driesvints changed the title Optimize eager loading when no keys to be loaded [10.x] Optimize eager loading when no keys to be loaded Sep 13, 2022
@taylorotwell taylorotwell merged commit 4d25fdc into laravel:master Sep 15, 2022
browner12 added a commit to browner12/framework that referenced this pull request Mar 19, 2025
we already pull the related model off of the `$query` in the constructor, so we can use it directly.

introduced in laravel#43724
taylorotwell pushed a commit that referenced this pull request Mar 19, 2025
we already pull the related model off of the `$query` in the constructor, so we can use it directly.

introduced in #43724
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.

4 participants