1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-19 12:00:55 +01:00

Modernize more paging/order queries

Summary:
Ref T7803. Removes some getReversePaging().

This also fixes `null` column handling, by adding an explicit `'null'` key with possible values "head" (put NULL before other values) or "tail" (put NULL after other values).

Maniphest has some glitchiness in paging through NULLs right now, but I believe it's all pre-existing and will be resolved when it fully converts. Diffusion is fully converted and pages through NULL correctly.

Test Plan:
  - Failed to identify any reason for ChangesetQuery to reverse paging.
  - Paged thorugh Diffusion.
  - Paged through Maniphest.
    - Maniphest has some issues when paging inside a NULL section, but these issues are preexisting and will be resolved later in this change sequence.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T7803

Differential Revision: https://secure.phabricator.com/D12371
This commit is contained in:
epriestley 2015-04-11 22:26:19 -07:00
parent 9c7c13ffc8
commit 4114560844
5 changed files with 230 additions and 213 deletions

View file

@ -150,8 +150,4 @@ final class DifferentialChangesetQuery
return 'PhabricatorDifferentialApplication'; return 'PhabricatorDifferentialApplication';
} }
protected function getReversePaging() {
return true;
}
} }

View file

@ -969,13 +969,11 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
} }
protected function buildPagingClause(AphrontDatabaseConnection $conn_r) { protected function buildPagingClause(AphrontDatabaseConnection $conn_r) {
$default = parent::buildPagingClause($conn_r);
$before_id = $this->getBeforeID(); $before_id = $this->getBeforeID();
$after_id = $this->getAfterID(); $after_id = $this->getAfterID();
if (!$before_id && !$after_id) { if (!$before_id && !$after_id) {
return $default; return '';
} }
$cursor_id = nonempty($before_id, $after_id); $cursor_id = nonempty($before_id, $after_id);
@ -1004,28 +1002,24 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
); );
break; break;
case self::GROUP_OWNER: case self::GROUP_OWNER:
$columns[] = array( $value = null;
'table' => 'task',
'column' => 'ownerOrdering',
'value' => strlen($group_id),
'type' => 'null',
);
if ($group_id) { if ($group_id) {
$paging_users = id(new PhabricatorPeopleQuery()) $paging_users = id(new PhabricatorPeopleQuery())
->setViewer($this->getViewer()) ->setViewer($this->getViewer())
->withPHIDs(array($group_id)) ->withPHIDs(array($group_id))
->execute(); ->execute();
if (!$paging_users) { if ($paging_users) {
return null; $value = head($paging_users)->getUsername();
}
} }
$columns[] = array( $columns[] = array(
'table' => 'task', 'table' => 'task',
'column' => 'ownerOrdering', 'column' => 'ownerOrdering',
'value' => head($paging_users)->getUsername(), 'value' => $value,
'type' => 'string', 'type' => 'string',
'null' => 'head',
'reverse' => true, 'reverse' => true,
); );
}
break; break;
case self::GROUP_STATUS: case self::GROUP_STATUS:
$columns[] = array( $columns[] = array(
@ -1036,28 +1030,25 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
); );
break; break;
case self::GROUP_PROJECT: case self::GROUP_PROJECT:
$columns[] = array( $value = null;
'table' => 'projectGroupName',
'column' => 'indexedObjectName',
'value' => strlen($group_id),
'type' => 'null',
);
if ($group_id) { if ($group_id) {
$paging_projects = id(new PhabricatorProjectQuery()) $paging_projects = id(new PhabricatorProjectQuery())
->setViewer($this->getViewer()) ->setViewer($this->getViewer())
->withPHIDs(array($group_id)) ->withPHIDs(array($group_id))
->execute(); ->execute();
if (!$paging_projects) { if ($paging_projects) {
return null; $value = head($paging_projects)->getName();
} }
}
$columns[] = array( $columns[] = array(
'table' => 'projectGroupName', 'table' => 'projectGroupName',
'column' => 'indexedObjectName', 'column' => 'indexedObjectName',
'value' => head($paging_projects)->getName(), 'value' => $value,
'type' => 'string', 'type' => 'string',
'null' => 'head',
'reverse' => true, 'reverse' => true,
); );
}
break; break;
default: default:
throw new Exception("Unknown group query '{$this->groupBy}'!"); throw new Exception("Unknown group query '{$this->groupBy}'!");

View file

@ -56,10 +56,10 @@ final class PhrequentUserTimeQuery
$this->setOrderVector(array('start', 'id')); $this->setOrderVector(array('start', 'id'));
break; break;
case self::ORDER_ENDED_ASC: case self::ORDER_ENDED_ASC:
$this->setOrderVector(array('-ongoing', '-end', '-id')); $this->setOrderVector(array('-end', '-id'));
break; break;
case self::ORDER_ENDED_DESC: case self::ORDER_ENDED_DESC:
$this->setOrderVector(array('ongoing', 'end', 'id')); $this->setOrderVector(array('end', 'id'));
break; break;
default: default:
throw new Exception(pht('Unknown order "%s".', $order)); throw new Exception(pht('Unknown order "%s".', $order));
@ -125,13 +125,10 @@ final class PhrequentUserTimeQuery
'column' => 'dateStarted', 'column' => 'dateStarted',
'type' => 'int', 'type' => 'int',
), ),
'ongoing' => array(
'column' => 'dateEnded',
'type' => 'null',
),
'end' => array( 'end' => array(
'column' => 'dateEnded', 'column' => 'dateEnded',
'type' => 'int', 'type' => 'int',
'null' => 'head',
), ),
); );
} }
@ -141,7 +138,6 @@ final class PhrequentUserTimeQuery
return array( return array(
'id' => $usertime->getID(), 'id' => $usertime->getID(),
'start' => $usertime->getDateStarted(), 'start' => $usertime->getDateStarted(),
'ongoing' => $usertime->getDateEnded(),
'end' => $usertime->getDateEnded(), 'end' => $usertime->getDateEnded(),
); );
} }

View file

@ -28,7 +28,6 @@ final class PhabricatorRepositoryQuery
const ORDER_CALLSIGN = 'order-callsign'; const ORDER_CALLSIGN = 'order-callsign';
const ORDER_NAME = 'order-name'; const ORDER_NAME = 'order-name';
const ORDER_SIZE = 'order-size'; const ORDER_SIZE = 'order-size';
private $order = self::ORDER_CREATED;
const HOSTED_PHABRICATOR = 'hosted-phab'; const HOSTED_PHABRICATOR = 'hosted-phab';
const HOSTED_REMOTE = 'hosted-remote'; const HOSTED_REMOTE = 'hosted-remote';
@ -126,7 +125,25 @@ final class PhabricatorRepositoryQuery
} }
public function setOrder($order) { public function setOrder($order) {
$this->order = $order; switch ($order) {
case self::ORDER_CREATED:
$this->setOrderVector(array('id'));
break;
case self::ORDER_COMMITTED:
$this->setOrderVector(array('committed', 'id'));
break;
case self::ORDER_CALLSIGN:
$this->setOrderVector(array('callsign'));
break;
case self::ORDER_NAME:
$this->setOrderVector(array('name', 'id'));
break;
case self::ORDER_SIZE:
$this->setOrderVector(array('size', 'id'));
break;
default:
throw new Exception(pht('Unknown order "%s".', $order));
}
return $this; return $this;
} }
@ -152,7 +169,7 @@ final class PhabricatorRepositoryQuery
$table->getTableName(), $table->getTableName(),
$this->buildJoinsClause($conn_r), $this->buildJoinsClause($conn_r),
$this->buildWhereClause($conn_r), $this->buildWhereClause($conn_r),
$this->buildCustomOrderClause($conn_r), $this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r)); $this->buildLimitClause($conn_r));
$repositories = $table->loadAllFromArray($data); $repositories = $table->loadAllFromArray($data);
@ -295,162 +312,97 @@ final class PhabricatorRepositoryQuery
return $repositories; return $repositories;
} }
protected function buildCustomOrderClause(AphrontDatabaseConnection $conn) { public function getPrimaryTableAlias() {
$parts = array(); return 'r';
}
$order = $this->order; public function getOrderableColumns() {
switch ($order) { return parent::getOrderableColumns() + array(
case self::ORDER_CREATED: 'committed' => array(
break;
case self::ORDER_COMMITTED:
$parts[] = array(
'table' => 's', 'table' => 's',
'column' => 'epoch', 'column' => 'epoch',
); 'type' => 'int',
break; 'null' => 'tail',
case self::ORDER_CALLSIGN: ),
$parts[] = array( 'callsign' => array(
'table' => 'r', 'table' => 'r',
'column' => 'callsign', 'column' => 'callsign',
'type' => 'string',
'unique' => true,
'reverse' => true, 'reverse' => true,
); ),
break; 'name' => array(
case self::ORDER_NAME:
$parts[] = array(
'table' => 'r', 'table' => 'r',
'column' => 'name', 'column' => 'name',
'type' => 'string',
'reverse' => true, 'reverse' => true,
); ),
break; 'size' => array(
case self::ORDER_SIZE:
$parts[] = array(
'table' => 's', 'table' => 's',
'column' => 'size', 'column' => 'size',
'type' => 'int',
'null' => 'tail',
),
); );
break;
default:
throw new Exception("Unknown order '{$order}!'");
} }
$parts[] = array( protected function willExecuteCursorQuery(
'table' => 'r', PhabricatorCursorPagedPolicyAwareQuery $query) {
'column' => 'id', $vector = $this->getOrderVector();
);
return $this->formatOrderClause($conn, $parts); if ($vector->containsKey('committed')) {
}
protected function loadCursorObject($id) {
$query = id(new PhabricatorRepositoryQuery())
->setViewer($this->getPagingViewer())
->withIDs(array((int)$id));
if ($this->order == self::ORDER_COMMITTED) {
$query->needMostRecentCommits(true); $query->needMostRecentCommits(true);
} }
if ($this->order == self::ORDER_SIZE) { if ($vector->containsKey('size')) {
$query->needCommitCounts(true); $query->needCommitCounts(true);
} }
$results = $query->execute();
return head($results);
} }
protected function buildPagingClause(AphrontDatabaseConnection $conn_r) { protected function getPagingValueMap($cursor, array $keys) {
$default = parent::buildPagingClause($conn_r); $repository = $this->loadCursorObject($cursor);
$before_id = $this->getBeforeID(); $map = array(
$after_id = $this->getAfterID(); 'id' => $repository->getID(),
'callsign' => $repository->getCallsign(),
'name' => $repository->getName(),
);
if (!$before_id && !$after_id) { foreach ($keys as $key) {
return $default; switch ($key) {
} case 'committed':
$commit = $repository->getMostRecentCommit();
$order = $this->order; if ($commit) {
if ($order == self::ORDER_CREATED) { $map[$key] = $commit->getEpoch();
return $default;
}
if ($before_id) {
$cursor = $this->loadCursorObject($before_id);
} else { } else {
$cursor = $this->loadCursorObject($after_id); $map[$key] = null;
}
break;
case 'size':
$count = $repository->getCommitCount();
if ($count) {
$map[$key] = $count;
} else {
$map[$key] = null;
}
break;
}
} }
if (!$cursor) { return $map;
return null;
}
$id_column = array(
'table' => 'r',
'column' => 'id',
'type' => 'int',
'value' => $cursor->getID(),
);
$columns = array();
switch ($order) {
case self::ORDER_COMMITTED:
$commit = $cursor->getMostRecentCommit();
if (!$commit) {
return null;
}
$columns[] = array(
'table' => 's',
'column' => 'epoch',
'type' => 'int',
'value' => $commit->getEpoch(),
);
$columns[] = $id_column;
break;
case self::ORDER_CALLSIGN:
$columns[] = array(
'table' => 'r',
'column' => 'callsign',
'type' => 'string',
'value' => $cursor->getCallsign(),
'reverse' => true,
);
break;
case self::ORDER_NAME:
$columns[] = array(
'table' => 'r',
'column' => 'name',
'type' => 'string',
'value' => $cursor->getName(),
'reverse' => true,
);
$columns[] = $id_column;
break;
case self::ORDER_SIZE:
$columns[] = array(
'table' => 's',
'column' => 'size',
'type' => 'int',
'value' => $cursor->getCommitCount(),
);
$columns[] = $id_column;
break;
default:
throw new Exception("Unknown order '{$order}'!");
}
return $this->buildPagingClauseFromMultipleColumns(
$conn_r,
$columns,
array(
'reversed' => ($this->getReversePaging() xor (bool)($before_id)),
));
} }
private function buildJoinsClause(AphrontDatabaseConnection $conn_r) { private function buildJoinsClause(AphrontDatabaseConnection $conn_r) {
$joins = array(); $joins = array();
$join_summary_table = $this->needCommitCounts || $join_summary_table = $this->needCommitCounts ||
$this->needMostRecentCommits || $this->needMostRecentCommits;
($this->order == self::ORDER_COMMITTED) ||
($this->order == self::ORDER_SIZE); $vector = $this->getOrderVector();
if ($vector->containsKey('committed') ||
$vector->containsKey('size')) {
$join_summary_table = true;
}
if ($join_summary_table) { if ($join_summary_table) {
$joins[] = qsprintf( $joins[] = qsprintf(
@ -554,6 +506,8 @@ final class PhabricatorRepositoryQuery
return $this->formatWhereClause($where); return $this->formatWhereClause($where);
} }
public function getQueryApplicationClass() { public function getQueryApplicationClass() {
return 'PhabricatorDiffusionApplication'; return 'PhabricatorDiffusionApplication';
} }

View file

@ -320,6 +320,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
'type' => 'string', 'type' => 'string',
'reverse' => 'optional bool', 'reverse' => 'optional bool',
'unique' => 'optional bool', 'unique' => 'optional bool',
'null' => 'optional string|null',
)); ));
} }
@ -336,10 +337,20 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
$last_key = last_key($columns); $last_key = last_key($columns);
foreach ($columns as $key => $column) { foreach ($columns as $key => $column) {
$type = $column['type']; $type = $column['type'];
$null = idx($column, 'null');
if ($column['value'] === null) {
if ($null) {
$value = null;
} else {
throw new Exception(
pht(
'Column "%s" has null value, but does not specify a null '.
'behavior.',
$key));
}
} else {
switch ($type) { switch ($type) {
case 'null':
$value = qsprintf($conn, '%d', ($column['value'] ? 0 : 1));
break;
case 'int': case 'int':
$value = qsprintf($conn, '%d', $column['value']); $value = qsprintf($conn, '%d', $column['value']);
break; break;
@ -350,7 +361,12 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
$value = qsprintf($conn, '%s', $column['value']); $value = qsprintf($conn, '%s', $column['value']);
break; break;
default: default:
throw new Exception("Unknown column type '{$type}'!"); throw new Exception(
pht(
'Column "%s" has unknown column type "%s".',
$column['column'],
$type));
}
} }
$is_column_reversed = idx($column, 'reverse', false); $is_column_reversed = idx($column, 'reverse', false);
@ -366,24 +382,68 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
$field = qsprintf($conn, '%T', $column_name); $field = qsprintf($conn, '%T', $column_name);
} }
if ($type == 'null') { $parts = array();
$field = qsprintf($conn, '(%Q IS NULL)', $field); if ($null) {
$can_page_if_null = ($null === 'head');
$can_page_if_nonnull = ($null === 'tail');
if ($reverse) {
$can_page_if_null = !$can_page_if_null;
$can_page_if_nonnull = !$can_page_if_nonnull;
} }
$clause[] = qsprintf( $subclause = null;
if ($can_page_if_null && $value === null) {
$parts[] = qsprintf(
$conn,
'(%Q IS NOT NULL)',
$field);
} else if ($can_page_if_nonnull && $value !== null) {
$parts[] = qsprintf(
$conn,
'(%Q IS NULL)',
$field);
}
}
if ($value !== null) {
$parts[] = qsprintf(
$conn, $conn,
'%Q %Q %Q', '%Q %Q %Q',
$field, $field,
$reverse ? '>' : '<', $reverse ? '>' : '<',
$value); $value);
$clauses[] = '('.implode(') AND (', $clause).')'; }
if ($parts) {
if (count($parts) > 1) {
$clause[] = '('.implode(') OR (', $parts).')';
} else {
$clause[] = head($parts);
}
}
if ($clause) {
if (count($clause) > 1) {
$clauses[] = '('.implode(') AND (', $clause).')';
} else {
$clauses[] = head($clause);
}
}
if ($value === null) {
$accumulated[] = qsprintf(
$conn,
'%Q IS NULL',
$field);
} else {
$accumulated[] = qsprintf( $accumulated[] = qsprintf(
$conn, $conn,
'%Q = %Q', '%Q = %Q',
$field, $field,
$value); $value);
} }
}
return '('.implode(') OR (', $clauses).')'; return '('.implode(') OR (', $clauses).')';
} }
@ -576,8 +636,28 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
$field = qsprintf($conn, '%T', $column); $field = qsprintf($conn, '%T', $column);
} }
if (idx($part, 'type') === 'null') { $null = idx($part, 'null');
$field = qsprintf($conn, '(%Q IS NULL)', $field); if ($null) {
switch ($null) {
case 'head':
$null_field = qsprintf($conn, '(%Q IS NULL)', $field);
break;
case 'tail':
$null_field = qsprintf($conn, '(%Q IS NOT NULL)', $field);
break;
default:
throw new Exception(
pht(
'NULL value "%s" is invalid. Valid values are "head" and '.
'"tail".',
$null));
}
if ($descending) {
$sql[] = qsprintf($conn, '%Q DESC', $null_field);
} else {
$sql[] = qsprintf($conn, '%Q ASC', $null_field);
}
} }
if ($descending) { if ($descending) {