Account for raw limits properly in CalendarEventQuery
Summary: Fixes T8613. This was pretty straightforward, I just never dug into it originally. `rawResultLimit = 0` just means "no limit", so the fix is to only apply a limit if it is set to some nonzero value. Also modernize a few pieces of code. Test Plan: I'm actually not sure this can actually be hit normally? I faked `setGenerateGhosts(true)` into an unrelated query, hit the fatal, then fixed it. Reviewers: lpriestley, chad Reviewed By: chad Maniphest Tasks: T8613 Differential Revision: https://secure.phabricator.com/D15653
This commit is contained in:
		| @@ -90,24 +90,11 @@ final class PhabricatorCalendarEventQuery | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected function loadPage() { |   protected function loadPage() { | ||||||
|     $table = new PhabricatorCalendarEvent(); |     $events = $this->loadStandardPage($this->newResultObject()); | ||||||
|     $conn_r = $table->establishConnection('r'); |  | ||||||
|     $viewer = $this->getViewer(); |     $viewer = $this->getViewer(); | ||||||
|  |  | ||||||
|     $data = queryfx_all( |  | ||||||
|       $conn_r, |  | ||||||
|       'SELECT event.* FROM %T event %Q %Q %Q %Q %Q', |  | ||||||
|       $table->getTableName(), |  | ||||||
|       $this->buildJoinClause($conn_r), |  | ||||||
|       $this->buildWhereClause($conn_r), |  | ||||||
|       $this->buildGroupClause($conn_r), |  | ||||||
|       $this->buildOrderClause($conn_r), |  | ||||||
|       $this->buildLimitClause($conn_r)); |  | ||||||
|  |  | ||||||
|     $events = $table->loadAllFromArray($data); |  | ||||||
|  |  | ||||||
|     foreach ($events as $event) { |     foreach ($events as $event) { | ||||||
|       $event->applyViewerTimezone($this->getViewer()); |       $event->applyViewerTimezone($viewer); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (!$this->generateGhosts) { |     if (!$this->generateGhosts) { | ||||||
| @@ -115,6 +102,15 @@ final class PhabricatorCalendarEventQuery | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     $enforced_end = null; |     $enforced_end = null; | ||||||
|  |     $raw_limit = $this->getRawResultLimit(); | ||||||
|  |  | ||||||
|  |     if (!$raw_limit && !$this->rangeEnd) { | ||||||
|  |       throw new Exception( | ||||||
|  |         pht( | ||||||
|  |           'Event queries which generate ghost events must include either a '. | ||||||
|  |           'result limit or an end date, because they may otherwise generate '. | ||||||
|  |           'an infinite number of results. This query has neither.')); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     foreach ($events as $key => $event) { |     foreach ($events as $key => $event) { | ||||||
|       $sequence_start = 0; |       $sequence_start = 0; | ||||||
| @@ -176,12 +172,12 @@ final class PhabricatorCalendarEventQuery | |||||||
|             $sequence_end++; |             $sequence_end++; | ||||||
|             $datetime->modify($modify_key); |             $datetime->modify($modify_key); | ||||||
|             $date = $datetime->format('U'); |             $date = $datetime->format('U'); | ||||||
|             if ($sequence_end > $this->getRawResultLimit() + $sequence_start) { |             if ($sequence_end > $raw_limit + $sequence_start) { | ||||||
|               break; |               break; | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } else { |         } else { | ||||||
|           $sequence_end = $this->getRawResultLimit() + $sequence_start; |           $sequence_end = $raw_limit + $sequence_start; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         $sequence_start = max(1, $sequence_start); |         $sequence_start = max(1, $sequence_start); | ||||||
| @@ -190,10 +186,17 @@ final class PhabricatorCalendarEventQuery | |||||||
|           $events[] = $event->generateNthGhost($index, $viewer); |           $events[] = $event->generateNthGhost($index, $viewer); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (count($events) >= $this->getRawResultLimit()) { |         // NOTE: We're slicing results every time because this makes it cheaper | ||||||
|           $events = msort($events, 'getDateFrom'); |         // to generate future ghosts. If we already have 100 events that occur | ||||||
|           $events = array_slice($events, 0, $this->getRawResultLimit(), true); |         // before July 1, we know we never need to generate ghosts after that | ||||||
|           $enforced_end = last($events)->getDateFrom(); |         // because they couldn't possibly ever appear in the result set. | ||||||
|  |  | ||||||
|  |         if ($raw_limit) { | ||||||
|  |           if (count($events) >= $raw_limit) { | ||||||
|  |             $events = msort($events, 'getDateFrom'); | ||||||
|  |             $events = array_slice($events, 0, $raw_limit, true); | ||||||
|  |             $enforced_end = last($events)->getDateFrom(); | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -251,61 +254,61 @@ final class PhabricatorCalendarEventQuery | |||||||
|     return $parts; |     return $parts; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { |   protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { | ||||||
|     $where = array(); |     $where = parent::buildWhereClauseParts($conn); | ||||||
|  |  | ||||||
|     if ($this->ids) { |     if ($this->ids) { | ||||||
|       $where[] = qsprintf( |       $where[] = qsprintf( | ||||||
|         $conn_r, |         $conn, | ||||||
|         'event.id IN (%Ld)', |         'event.id IN (%Ld)', | ||||||
|         $this->ids); |         $this->ids); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if ($this->phids) { |     if ($this->phids) { | ||||||
|       $where[] = qsprintf( |       $where[] = qsprintf( | ||||||
|         $conn_r, |         $conn, | ||||||
|         'event.phid IN (%Ls)', |         'event.phid IN (%Ls)', | ||||||
|         $this->phids); |         $this->phids); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if ($this->rangeBegin) { |     if ($this->rangeBegin) { | ||||||
|       $where[] = qsprintf( |       $where[] = qsprintf( | ||||||
|         $conn_r, |         $conn, | ||||||
|         'event.dateTo >= %d OR event.isRecurring = 1', |         'event.dateTo >= %d OR event.isRecurring = 1', | ||||||
|         $this->rangeBegin); |         $this->rangeBegin); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if ($this->rangeEnd) { |     if ($this->rangeEnd) { | ||||||
|       $where[] = qsprintf( |       $where[] = qsprintf( | ||||||
|         $conn_r, |         $conn, | ||||||
|         'event.dateFrom <= %d', |         'event.dateFrom <= %d', | ||||||
|         $this->rangeEnd); |         $this->rangeEnd); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if ($this->inviteePHIDs !== null) { |     if ($this->inviteePHIDs !== null) { | ||||||
|       $where[] = qsprintf( |       $where[] = qsprintf( | ||||||
|         $conn_r, |         $conn, | ||||||
|         'invitee.inviteePHID IN (%Ls)', |         'invitee.inviteePHID IN (%Ls)', | ||||||
|         $this->inviteePHIDs); |         $this->inviteePHIDs); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if ($this->creatorPHIDs) { |     if ($this->creatorPHIDs) { | ||||||
|       $where[] = qsprintf( |       $where[] = qsprintf( | ||||||
|         $conn_r, |         $conn, | ||||||
|         'event.userPHID IN (%Ls)', |         'event.userPHID IN (%Ls)', | ||||||
|         $this->creatorPHIDs); |         $this->creatorPHIDs); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if ($this->isCancelled !== null) { |     if ($this->isCancelled !== null) { | ||||||
|       $where[] = qsprintf( |       $where[] = qsprintf( | ||||||
|         $conn_r, |         $conn, | ||||||
|         'event.isCancelled = %d', |         'event.isCancelled = %d', | ||||||
|         (int)$this->isCancelled); |         (int)$this->isCancelled); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if ($this->eventsWithNoParent == true) { |     if ($this->eventsWithNoParent == true) { | ||||||
|       $where[] = qsprintf( |       $where[] = qsprintf( | ||||||
|         $conn_r, |         $conn, | ||||||
|         'event.instanceOfEventPHID IS NULL'); |         'event.instanceOfEventPHID IS NULL'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -314,20 +317,19 @@ final class PhabricatorCalendarEventQuery | |||||||
|  |  | ||||||
|       foreach ($this->instanceSequencePairs as $pair) { |       foreach ($this->instanceSequencePairs as $pair) { | ||||||
|         $sql[] = qsprintf( |         $sql[] = qsprintf( | ||||||
|           $conn_r, |           $conn, | ||||||
|           '(event.instanceOfEventPHID = %s AND event.sequenceIndex = %d)', |           '(event.instanceOfEventPHID = %s AND event.sequenceIndex = %d)', | ||||||
|           $pair[0], |           $pair[0], | ||||||
|           $pair[1]); |           $pair[1]); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       $where[] = qsprintf( |       $where[] = qsprintf( | ||||||
|         $conn_r, |         $conn, | ||||||
|         '%Q', |         '%Q', | ||||||
|         implode(' OR ', $sql)); |         implode(' OR ', $sql)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     $where[] = $this->buildPagingClause($conn_r); |     return $where; | ||||||
|  |  | ||||||
|     return $this->formatWhereClause($where); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected function getPrimaryTableAlias() { |   protected function getPrimaryTableAlias() { | ||||||
|   | |||||||
| @@ -412,6 +412,10 @@ final class PhabricatorPeopleQuery | |||||||
|     $min_range = PhabricatorTime::getNow(); |     $min_range = PhabricatorTime::getNow(); | ||||||
|     $max_range = $min_range + phutil_units('72 hours in seconds'); |     $max_range = $min_range + phutil_units('72 hours in seconds'); | ||||||
|  |  | ||||||
|  |     // NOTE: We don't need to generate ghosts here, because we only care if | ||||||
|  |     // the user is attending, and you can't attend a ghost event: RSVP'ing | ||||||
|  |     // to it creates a real event. | ||||||
|  |  | ||||||
|     $events = id(new PhabricatorCalendarEventQuery()) |     $events = id(new PhabricatorCalendarEventQuery()) | ||||||
|       ->setViewer(PhabricatorUser::getOmnipotentUser()) |       ->setViewer(PhabricatorUser::getOmnipotentUser()) | ||||||
|       ->withInvitedPHIDs(array_keys($rebuild)) |       ->withInvitedPHIDs(array_keys($rebuild)) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 epriestley
					epriestley