Refactor sticky order clause into a subquery

Based on some limited testing, using a subquery seems to outperform a
join in this case (the join was invoking a temporary table, which is
always a bad sign).

This also adds logic to fix a bug where sticky discussions would remain
at the top even when marked as read using the "mark all as read" button.
I thought we had an open issue for this somewhere, but I can't seem to
find one. ref #988 #1003
This commit is contained in:
Toby Zerner 2017-11-08 11:34:37 +10:30
parent 86cc5fe9c8
commit 7ff57e1ba4

View File

@ -45,10 +45,17 @@ class PinStickiedDiscussionsToTop
$search = $event->search;
$query = $search->getQuery();
// TODO: ideally we might like to consider an event in core that is
// fired before the sort criteria is applied to the query (ie.
// immediately after gambits are applied) so that we can add the
// following order logic to the start without using array_unshift.
if (! is_array($query->orders)) {
$query->orders = [];
}
// If we are viewing a specific tag, then pin all stickied
// discussions to the top no matter what.
foreach ($search->getActiveGambits() as $gambit) {
if ($gambit instanceof TagGambit) {
array_unshift($query->orders, ['column' => 'is_sticky', 'direction' => 'desc']);
@ -57,21 +64,28 @@ class PinStickiedDiscussionsToTop
}
}
$query->leftJoin('users_discussions', function ($join) use ($search) {
$join->on('users_discussions.discussion_id', '=', 'discussions.id')
->where('discussions.is_sticky', '=', true)
->where('users_discussions.user_id', '=', $search->getActor()->id);
});
// TODO: Might be quicker to do a subquery in the order clause than a join?
$grammar = $query->getGrammar();
$readNumber = $grammar->wrap('users_discussions.read_number');
$lastPostNumber = $grammar->wrap('discussions.last_post_number');
// Otherwise, if we are viewing "all discussions" or similar, only
// pin stickied discussions to the top if they are unread. To do
// this we construct an order clause containing a subquery which
// determines whether or not the discussion is unread.
$subquery = $query->newQuery()
->selectRaw(1)
->from('users_discussions as sticky')
->whereRaw('sticky.discussion_id = discussions.id')
->where('sticky.user_id', '=', $search->getActor()->id)
->where(function ($query) {
$query->whereNull('sticky.read_number')
->orWhereRaw('discussions.last_post_number > sticky.read_number');
})
->where('discussions.last_time', '>', $search->getActor()->read_time ?: 0);
array_unshift($query->orders, [
'type' => 'raw',
'sql' => "(is_sticky AND ($readNumber IS NULL OR $lastPostNumber > $readNumber)) desc"
'sql' => "(is_sticky and exists ({$subquery->toSql()})) desc"
]);
$orderBindings = $query->getRawBindings()['order'];
$query->setBindings(array_merge($subquery->getBindings(), $orderBindings), 'order');
}
}
}