perf: group global search columns of the same relation into one sub-query - #3299
perf: group global search columns of the same relation into one sub-query#3299OzanKurt wants to merge 1 commit into
Conversation
|
Please resolve the conflicts, thanks! |
…uery
Global search compiled one where has query per searchable column, so a
relation with n searchable columns produced n exists sub-queries for the
same table. Searchable columns are now grouped per relation and compiled
into a single sub-query:
where exists (
select * from users
where posts.user_id = users.id
and (lower(users.name) like ? or lower(users.email) like ?)
)
The column conditions are wrapped in a nested where so the or does not
leak out of the relation constraint. Column search and ordering are not
affected.
Closes yajra#2519
5dafc3d to
9a43d7b
Compare
|
|
Rebased on v13.2.0 and resolved, the conflict was only in the |
|
Heads up on an interaction with #3300: both PRs rewrite #3300 adds The correct resolution is to call $relation = $this->resolveRelationName(array_shift($parts));
// ...
$relation = $this->resolveRelationName(implode('.', $parts));I merged both branches locally with that resolution and the suite is green, 182 tests, PHPStan clean. Whichever of the two you merge first, I will push the follow up to the other one right away, so there is no need to resolve it by hand. |



Closes #2519
Problem
Global search compiles one query per searchable column. For an eager loaded relation that means one
existssub-query per column, all hitting the same table:Column search and ordering already resolve relation columns once, so global search was the odd one out, and the cost grows with every searchable column of the relation.
Solution
Searchable columns are now grouped per relation before being compiled, so each relation produces a single sub-query:
The column conditions are wrapped in a nested
whereso theorcannot leak out of the relation constraint. That wrapper is only added when a relation has more than one searchable column, keeping the generated SQL identical to before for every single column case.The original suggestion in the issue was to call
resolveRelationColumn()inglobalSearch(). That turns the sub-query into a join, which also multiplies rows forhasMany/belongsToManyrelations and changesrecordsFiltered. Grouping keeps the current semantics and only removes the duplicated sub-queries, which is what was agreed on in the discussion.Changes
QueryDataTable::globalSearch()now delegates tocompileGlobalSearch(), which loops the searchable columns throughcompileGlobalSearchColumn(). Behaviour is unchanged for the query engine.EloquentDataTable::compileGlobalSearch()groups the columns by relation, preserving the order of first appearance, and compiles each group into onewhereHas/whereHasMorph.EloquentDataTable::compileQuerySearch()was split intoresolveSearchableRelation()(which relation, if any, a column belongs to) andcompileRelationSearch()(compile one relation with n columns), so single column search and grouped global search share one code path.Notes
filterColumn()callbacks, columns of relations that are not eager loaded, and morph relations keep their existing per relation handling.tests/Unit/GlobalSearchRelationTest.phpcovers the grouping, one sub-query per relation, the untouched non eager loaded case, theand (... or ...)precedence, and that the result set is unchanged. Three of these tests fail on the current implementation.composer stanpasses and the existing relation tests (BelongsTo,HasMany,HasOne,HasOneThrough,BelongsToMany,MorphTo,DeepRelation,ArrayNotationRelation) pass unchanged.