1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 14:52:41 +01:00

Fix an issue where paginating notifications could fail a GROUP BY test

Summary:
Ref T13623. When paginating notifications, we may currently construct a query which:

  - loads from non-unique rows; and
  - returns multiple results.

In particular, `chronologicalKey` isn't unique across the whole table (only for a given viewer). We can get away with this because no user-facing view of notifications is truly "every notification for every viewer" today.

One fix would be to implicitly force the paging query to include `withUserPHIDs(viewerPHID)`, but puruse a slightly more general fix:

  - Load only unique stories.
  - Explictly limit the pagination subquery to one result.

Test Plan:
  - Set page size to 1, inserted duplicate notifications of all stories for another user, clicked "Next", got the GROUP BY error.
  - Applied the "only load unique stories" part of the change, got a "expected one row" error instead.
  - Applied the "limit 1" part of the change, got a second page of notifications.

Maniphest Tasks: T13623

Differential Revision: https://secure.phabricator.com/D21577
This commit is contained in:
epriestley 2021-02-26 10:41:53 -08:00
parent 10162ad43b
commit 0a3093ef9c

View file

@ -63,17 +63,25 @@ final class PhabricatorNotificationQuery
$this->buildWhereClause($conn), $this->buildWhereClause($conn),
$this->buildLimitClause($conn)); $this->buildLimitClause($conn));
$viewed_map = ipull($data, 'hasViewed', 'chronologicalKey'); // See T13623. Although most queries for notifications return unique
// stories, this isn't a guarantee.
$story_map = ipull($data, null, 'chronologicalKey');
$stories = PhabricatorFeedStory::loadAllFromRows( $stories = PhabricatorFeedStory::loadAllFromRows(
$data, $story_map,
$this->getViewer()); $this->getViewer());
$stories = mpull($stories, null, 'getChronologicalKey');
foreach ($stories as $key => $story) { $results = array();
$story->setHasViewed($viewed_map[$key]); foreach ($data as $row) {
$story_key = $row['chronologicalKey'];
$has_viewed = $row['hasViewed'];
$results[] = id(clone $stories[$story_key])
->setHasViewed($has_viewed);
} }
return $stories; return $results;
} }
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
@ -145,7 +153,11 @@ final class PhabricatorNotificationQuery
protected function applyExternalCursorConstraintsToQuery( protected function applyExternalCursorConstraintsToQuery(
PhabricatorCursorPagedPolicyAwareQuery $subquery, PhabricatorCursorPagedPolicyAwareQuery $subquery,
$cursor) { $cursor) {
$subquery->withKeys(array($cursor));
$subquery
->withKeys(array($cursor))
->setLimit(1);
} }
protected function newExternalCursorStringForResult($object) { protected function newExternalCursorStringForResult($object) {