Changeset View
Changeset View
Standalone View
Standalone View
src/applications/diffusion/query/DiffusionCommitQuery.php
Show All 9 Lines | final class DiffusionCommitQuery | ||||
private $identifiers; | private $identifiers; | ||||
private $repositoryIDs; | private $repositoryIDs; | ||||
private $repositoryPHIDs; | private $repositoryPHIDs; | ||||
private $identifierMap; | private $identifierMap; | ||||
private $needAuditRequests; | private $needAuditRequests; | ||||
private $auditIDs; | private $auditIDs; | ||||
private $auditorPHIDs; | private $auditorPHIDs; | ||||
private $auditAwaitingUser; | private $needsAuditByPHIDs; | ||||
private $auditStatus; | private $auditStatus; | ||||
private $epochMin; | private $epochMin; | ||||
private $epochMax; | private $epochMax; | ||||
private $importing; | private $importing; | ||||
const AUDIT_STATUS_ANY = 'audit-status-any'; | const AUDIT_STATUS_ANY = 'audit-status-any'; | ||||
const AUDIT_STATUS_OPEN = 'audit-status-open'; | const AUDIT_STATUS_OPEN = 'audit-status-open'; | ||||
const AUDIT_STATUS_CONCERN = 'audit-status-concern'; | const AUDIT_STATUS_CONCERN = 'audit-status-concern'; | ||||
▲ Show 20 Lines • Show All 71 Lines • ▼ Show 20 Lines | public function needCommitData($need) { | ||||
return $this; | return $this; | ||||
} | } | ||||
public function needAuditRequests($need) { | public function needAuditRequests($need) { | ||||
$this->needAuditRequests = $need; | $this->needAuditRequests = $need; | ||||
return $this; | return $this; | ||||
} | } | ||||
/** | |||||
* Returns true if we should join the audit table, either because we're | |||||
* interested in the information if it's available or because matching rows | |||||
* must always have it. | |||||
*/ | |||||
private function shouldJoinAudits() { | |||||
return $this->auditStatus || | |||||
$this->rowsMustHaveAudits(); | |||||
} | |||||
/** | |||||
* Return true if we should `JOIN` (vs `LEFT JOIN`) the audit table, because | |||||
* matching commits will always have audit rows. | |||||
*/ | |||||
private function rowsMustHaveAudits() { | |||||
return | |||||
$this->auditIDs || | |||||
$this->auditorPHIDs || | |||||
$this->auditAwaitingUser; | |||||
} | |||||
public function withAuditIDs(array $ids) { | public function withAuditIDs(array $ids) { | ||||
$this->auditIDs = $ids; | $this->auditIDs = $ids; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function withAuditorPHIDs(array $auditor_phids) { | public function withAuditorPHIDs(array $auditor_phids) { | ||||
$this->auditorPHIDs = $auditor_phids; | $this->auditorPHIDs = $auditor_phids; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function withAuditAwaitingUser(PhabricatorUser $user) { | public function withNeedsAuditByPHIDs(array $needs_phids) { | ||||
$this->auditAwaitingUser = $user; | $this->needsAuditByPHIDs = $needs_phids; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function withAuditStatus($status) { | public function withAuditStatus($status) { | ||||
$this->auditStatus = $status; | $this->auditStatus = $status; | ||||
return $this; | return $this; | ||||
} | } | ||||
Show All 23 Lines | final class DiffusionCommitQuery | ||||
} | } | ||||
protected function willExecute() { | protected function willExecute() { | ||||
if ($this->identifierMap === null) { | if ($this->identifierMap === null) { | ||||
$this->identifierMap = array(); | $this->identifierMap = array(); | ||||
} | } | ||||
} | } | ||||
protected function loadPage() { | public function newResultObject() { | ||||
$table = new PhabricatorRepositoryCommit(); | return new PhabricatorRepositoryCommit(); | ||||
$conn_r = $table->establishConnection('r'); | } | ||||
$data = queryfx_all( | |||||
$conn_r, | |||||
'SELECT commit.* FROM %T commit %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)); | |||||
return $table->loadAllFromArray($data); | protected function loadPage() { | ||||
return $this->loadStandardPage($this->newResultObject()); | |||||
} | } | ||||
protected function willFilterPage(array $commits) { | protected function willFilterPage(array $commits) { | ||||
$repository_ids = mpull($commits, 'getRepositoryID', 'getRepositoryID'); | $repository_ids = mpull($commits, 'getRepositoryID', 'getRepositoryID'); | ||||
$repos = id(new PhabricatorRepositoryQuery()) | $repos = id(new PhabricatorRepositoryQuery()) | ||||
->setViewer($this->getViewer()) | ->setViewer($this->getViewer()) | ||||
->withIDs($repository_ids) | ->withIDs($repository_ids) | ||||
->execute(); | ->execute(); | ||||
▲ Show 20 Lines • Show All 90 Lines • ▼ Show 20 Lines | if ($this->needAuditRequests || $this->shouldJoinAudits()) { | ||||
$audit_request->attachCommit($commit); | $audit_request->attachCommit($commit); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
return $commits; | return $commits; | ||||
} | } | ||||
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { | protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { | ||||
$where = array(); | $where = parent::buildWhereClauseParts($conn); | ||||
if ($this->repositoryPHIDs !== null) { | if ($this->repositoryPHIDs !== null) { | ||||
$map_repositories = id(new PhabricatorRepositoryQuery()) | $map_repositories = id(new PhabricatorRepositoryQuery()) | ||||
->setViewer($this->getViewer()) | ->setViewer($this->getViewer()) | ||||
->withPHIDs($this->repositoryPHIDs) | ->withPHIDs($this->repositoryPHIDs) | ||||
->execute(); | ->execute(); | ||||
if (!$map_repositories) { | if (!$map_repositories) { | ||||
throw new PhabricatorEmptyQueryException(); | throw new PhabricatorEmptyQueryException(); | ||||
} | } | ||||
$repository_ids = mpull($map_repositories, 'getID'); | $repository_ids = mpull($map_repositories, 'getID'); | ||||
if ($this->repositoryIDs !== null) { | if ($this->repositoryIDs !== null) { | ||||
$repository_ids = array_merge($repository_ids, $this->repositoryIDs); | $repository_ids = array_merge($repository_ids, $this->repositoryIDs); | ||||
} | } | ||||
$this->withRepositoryIDs($repository_ids); | $this->withRepositoryIDs($repository_ids); | ||||
} | } | ||||
if ($this->ids !== null) { | if ($this->ids !== null) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'commit.id IN (%Ld)', | 'commit.id IN (%Ld)', | ||||
$this->ids); | $this->ids); | ||||
} | } | ||||
if ($this->phids !== null) { | if ($this->phids !== null) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'commit.phid IN (%Ls)', | 'commit.phid IN (%Ls)', | ||||
$this->phids); | $this->phids); | ||||
} | } | ||||
if ($this->repositoryIDs !== null) { | if ($this->repositoryIDs !== null) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'commit.repositoryID IN (%Ld)', | 'commit.repositoryID IN (%Ld)', | ||||
$this->repositoryIDs); | $this->repositoryIDs); | ||||
} | } | ||||
if ($this->authorPHIDs !== null) { | if ($this->authorPHIDs !== null) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'commit.authorPHID IN (%Ls)', | 'commit.authorPHID IN (%Ls)', | ||||
$this->authorPHIDs); | $this->authorPHIDs); | ||||
} | } | ||||
if ($this->epochMin !== null) { | if ($this->epochMin !== null) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'commit.epoch >= %d', | 'commit.epoch >= %d', | ||||
$this->epochMin); | $this->epochMin); | ||||
} | } | ||||
if ($this->epochMax !== null) { | if ($this->epochMax !== null) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'commit.epoch <= %d', | 'commit.epoch <= %d', | ||||
$this->epochMax); | $this->epochMax); | ||||
} | } | ||||
if ($this->importing !== null) { | if ($this->importing !== null) { | ||||
if ($this->importing) { | if ($this->importing) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'(commit.importStatus & %d) != %d', | '(commit.importStatus & %d) != %d', | ||||
PhabricatorRepositoryCommit::IMPORTED_ALL, | PhabricatorRepositoryCommit::IMPORTED_ALL, | ||||
PhabricatorRepositoryCommit::IMPORTED_ALL); | PhabricatorRepositoryCommit::IMPORTED_ALL); | ||||
} else { | } else { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'(commit.importStatus & %d) = %d', | '(commit.importStatus & %d) = %d', | ||||
PhabricatorRepositoryCommit::IMPORTED_ALL, | PhabricatorRepositoryCommit::IMPORTED_ALL, | ||||
PhabricatorRepositoryCommit::IMPORTED_ALL); | PhabricatorRepositoryCommit::IMPORTED_ALL); | ||||
} | } | ||||
} | } | ||||
if ($this->identifiers !== null) { | if ($this->identifiers !== null) { | ||||
$min_unqualified = PhabricatorRepository::MINIMUM_UNQUALIFIED_HASH; | $min_unqualified = PhabricatorRepository::MINIMUM_UNQUALIFIED_HASH; | ||||
Show All 26 Lines | if ($this->identifiers !== null) { | ||||
); | ); | ||||
} | } | ||||
} | } | ||||
$sql = array(); | $sql = array(); | ||||
foreach ($bare as $identifier) { | foreach ($bare as $identifier) { | ||||
$sql[] = qsprintf( | $sql[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'(commit.commitIdentifier LIKE %> AND '. | '(commit.commitIdentifier LIKE %> AND '. | ||||
'LENGTH(commit.commitIdentifier) = 40)', | 'LENGTH(commit.commitIdentifier) = 40)', | ||||
$identifier); | $identifier); | ||||
} | } | ||||
if ($refs) { | if ($refs) { | ||||
$callsigns = ipull($refs, 'callsign'); | $callsigns = ipull($refs, 'callsign'); | ||||
Show All 11 Lines | if ($this->identifiers !== null) { | ||||
continue; | continue; | ||||
} | } | ||||
if ($repo->isSVN()) { | if ($repo->isSVN()) { | ||||
if (!ctype_digit($ref['identifier'])) { | if (!ctype_digit($ref['identifier'])) { | ||||
continue; | continue; | ||||
} | } | ||||
$sql[] = qsprintf( | $sql[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'(commit.repositoryID = %d AND commit.commitIdentifier = %s)', | '(commit.repositoryID = %d AND commit.commitIdentifier = %s)', | ||||
$repo->getID(), | $repo->getID(), | ||||
// NOTE: Because the 'commitIdentifier' column is a string, MySQL | // NOTE: Because the 'commitIdentifier' column is a string, MySQL | ||||
// ignores the index if we hand it an integer. Hand it a string. | // ignores the index if we hand it an integer. Hand it a string. | ||||
// See T3377. | // See T3377. | ||||
(int)$ref['identifier']); | (int)$ref['identifier']); | ||||
} else { | } else { | ||||
if (strlen($ref['identifier']) < $min_qualified) { | if (strlen($ref['identifier']) < $min_qualified) { | ||||
continue; | continue; | ||||
} | } | ||||
$sql[] = qsprintf( | $sql[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'(commit.repositoryID = %d AND commit.commitIdentifier LIKE %>)', | '(commit.repositoryID = %d AND commit.commitIdentifier LIKE %>)', | ||||
$repo->getID(), | $repo->getID(), | ||||
$ref['identifier']); | $ref['identifier']); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if (!$sql) { | if (!$sql) { | ||||
// If we discarded all possible identifiers (e.g., they all referenced | // If we discarded all possible identifiers (e.g., they all referenced | ||||
// bogus repositories or were all too short), make sure the query finds | // bogus repositories or were all too short), make sure the query finds | ||||
// nothing. | // nothing. | ||||
throw new PhabricatorEmptyQueryException( | throw new PhabricatorEmptyQueryException( | ||||
pht('No commit identifiers.')); | pht('No commit identifiers.')); | ||||
} | } | ||||
$where[] = '('.implode(' OR ', $sql).')'; | $where[] = '('.implode(' OR ', $sql).')'; | ||||
} | } | ||||
if ($this->auditIDs !== null) { | if ($this->auditIDs !== null) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'audit.id IN (%Ld)', | 'audit.id IN (%Ld)', | ||||
$this->auditIDs); | $this->auditIDs); | ||||
} | } | ||||
if ($this->auditorPHIDs !== null) { | if ($this->auditorPHIDs !== null) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'audit.auditorPHID IN (%Ls)', | 'audit.auditorPHID IN (%Ls)', | ||||
$this->auditorPHIDs); | $this->auditorPHIDs); | ||||
} | } | ||||
if ($this->auditAwaitingUser) { | if ($this->needsAuditByPHIDs !== null) { | ||||
$awaiting_user_phid = $this->auditAwaitingUser->getPHID(); | $where[] = qsprintf( | ||||
// Exclude package and project audits associated with commits where | $conn, | ||||
// the user is the author. | 'needs.auditorPHID IN (%Ls)', | ||||
$where[] = qsprintf( | $this->needsAuditByPHIDs); | ||||
$conn_r, | |||||
'(commit.authorPHID IS NULL OR commit.authorPHID != %s) | |||||
OR (audit.auditorPHID = %s)', | |||||
$awaiting_user_phid, | |||||
$awaiting_user_phid); | |||||
} | } | ||||
$status = $this->auditStatus; | $status = $this->auditStatus; | ||||
if ($status !== null) { | if ($status !== null) { | ||||
switch ($status) { | switch ($status) { | ||||
case self::AUDIT_STATUS_PARTIAL: | case self::AUDIT_STATUS_PARTIAL: | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'commit.auditStatus = %d', | 'commit.auditStatus = %d', | ||||
PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED); | PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED); | ||||
break; | break; | ||||
case self::AUDIT_STATUS_ACCEPTED: | case self::AUDIT_STATUS_ACCEPTED: | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'commit.auditStatus = %d', | 'commit.auditStatus = %d', | ||||
PhabricatorAuditCommitStatusConstants::FULLY_AUDITED); | PhabricatorAuditCommitStatusConstants::FULLY_AUDITED); | ||||
break; | break; | ||||
case self::AUDIT_STATUS_CONCERN: | case self::AUDIT_STATUS_CONCERN: | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'audit.auditStatus = %s', | 'status.auditStatus = %s', | ||||
PhabricatorAuditStatusConstants::CONCERNED); | PhabricatorAuditStatusConstants::CONCERNED); | ||||
break; | break; | ||||
case self::AUDIT_STATUS_OPEN: | case self::AUDIT_STATUS_OPEN: | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'audit.auditStatus in (%Ls)', | 'status.auditStatus in (%Ls)', | ||||
PhabricatorAuditStatusConstants::getOpenStatusConstants()); | PhabricatorAuditStatusConstants::getOpenStatusConstants()); | ||||
if ($this->auditAwaitingUser) { | |||||
$where[] = qsprintf( | |||||
$conn_r, | |||||
'awaiting.auditStatus IS NULL OR awaiting.auditStatus != %s', | |||||
PhabricatorAuditStatusConstants::RESIGNED); | |||||
} | |||||
break; | break; | ||||
case self::AUDIT_STATUS_ANY: | case self::AUDIT_STATUS_ANY: | ||||
break; | break; | ||||
default: | default: | ||||
$valid = array( | $valid = array( | ||||
self::AUDIT_STATUS_ANY, | self::AUDIT_STATUS_ANY, | ||||
self::AUDIT_STATUS_OPEN, | self::AUDIT_STATUS_OPEN, | ||||
self::AUDIT_STATUS_CONCERN, | self::AUDIT_STATUS_CONCERN, | ||||
self::AUDIT_STATUS_ACCEPTED, | self::AUDIT_STATUS_ACCEPTED, | ||||
self::AUDIT_STATUS_PARTIAL, | self::AUDIT_STATUS_PARTIAL, | ||||
); | ); | ||||
throw new Exception( | throw new Exception( | ||||
pht( | pht( | ||||
"Unknown audit status '%s'! Valid statuses are: %s.", | "Unknown audit status '%s'! Valid statuses are: %s.", | ||||
$status, | $status, | ||||
implode(', ', $valid))); | implode(', ', $valid))); | ||||
} | } | ||||
} | } | ||||
$where[] = $this->buildPagingClause($conn_r); | return $where; | ||||
return $this->formatWhereClause($where); | |||||
} | } | ||||
protected function didFilterResults(array $filtered) { | protected function didFilterResults(array $filtered) { | ||||
if ($this->identifierMap) { | if ($this->identifierMap) { | ||||
foreach ($this->identifierMap as $name => $commit) { | foreach ($this->identifierMap as $name => $commit) { | ||||
if (isset($filtered[$commit->getPHID()])) { | if (isset($filtered[$commit->getPHID()])) { | ||||
unset($this->identifierMap[$name]); | unset($this->identifierMap[$name]); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { | private function shouldJoinStatus() { | ||||
$joins = array(); | return $this->auditStatus; | ||||
} | |||||
private function shouldJoinAudits() { | |||||
return $this->auditIDs || $this->auditorPHIDs; | |||||
} | |||||
private function shouldJoinNeeds() { | |||||
return $this->needsAuditByPHIDs; | |||||
} | |||||
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { | |||||
$join = parent::buildJoinClauseParts($conn); | |||||
$audit_request = new PhabricatorRepositoryAuditRequest(); | $audit_request = new PhabricatorRepositoryAuditRequest(); | ||||
if ($this->shouldJoinStatus()) { | |||||
$join[] = qsprintf( | |||||
$conn, | |||||
'LEFT JOIN %T status ON commit.phid = status.commitPHID', | |||||
$audit_request->getTableName()); | |||||
} | |||||
if ($this->shouldJoinAudits()) { | if ($this->shouldJoinAudits()) { | ||||
$joins[] = qsprintf( | $join[] = qsprintf( | ||||
$conn_r, | $conn, | ||||
'%Q %T audit ON commit.phid = audit.commitPHID', | 'JOIN %T audit ON commit.phid = audit.commitPHID', | ||||
($this->rowsMustHaveAudits() ? 'JOIN' : 'LEFT JOIN'), | |||||
$audit_request->getTableName()); | $audit_request->getTableName()); | ||||
} | } | ||||
if ($this->auditAwaitingUser) { | if ($this->shouldJoinNeeds()) { | ||||
// Join the request table on the awaiting user's requests, so we can | $join[] = qsprintf( | ||||
// filter out package and project requests which the user has resigned | $conn, | ||||
// from. | 'JOIN %T needs ON commit.phid = needs.commitPHID | ||||
$joins[] = qsprintf( | AND needs.auditStatus IN (%Ls)', | ||||
$conn_r, | |||||
'LEFT JOIN %T awaiting ON audit.commitPHID = awaiting.commitPHID AND | |||||
awaiting.auditorPHID = %s', | |||||
$audit_request->getTableName(), | $audit_request->getTableName(), | ||||
$this->auditAwaitingUser->getPHID()); | array( | ||||
PhabricatorAuditStatusConstants::AUDIT_REQUESTED, | |||||
PhabricatorAuditStatusConstants::AUDIT_REQUIRED, | |||||
)); | |||||
} | } | ||||
if ($joins) { | return $join; | ||||
return implode(' ', $joins); | |||||
} else { | |||||
return ''; | |||||
} | |||||
} | } | ||||
protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { | protected function shouldGroupQueryResultRows() { | ||||
$should_group = $this->shouldJoinAudits(); | if ($this->shouldJoinStatus()) { | ||||
return true; | |||||
} | |||||
// TODO: Currently, the audit table is missing a unique key, so we may | if ($this->shouldJoinAudits()) { | ||||
// require a GROUP BY if we perform this join. See T1768. This can be | return true; | ||||
// removed once the table has the key. | |||||
if ($this->auditAwaitingUser) { | |||||
$should_group = true; | |||||
} | } | ||||
if ($should_group) { | if ($this->shouldJoinNeeds()) { | ||||
return 'GROUP BY commit.id'; | return true; | ||||
} else { | |||||
return ''; | |||||
} | } | ||||
return parent::shouldGroupQueryResultRows(); | |||||
} | } | ||||
public function getQueryApplicationClass() { | public function getQueryApplicationClass() { | ||||
return 'PhabricatorDiffusionApplication'; | return 'PhabricatorDiffusionApplication'; | ||||
} | } | ||||
public function getOrderableColumns() { | |||||
return parent::getOrderableColumns() + array( | |||||
'epoch' => array( | |||||
'table' => $this->getPrimaryTableAlias(), | |||||
'column' => 'epoch', | |||||
'type' => 'int', | |||||
'reverse' => false, | |||||
), | |||||
); | |||||
} | |||||
protected function getPagingValueMap($cursor, array $keys) { | |||||
$commit = $this->loadCursorObject($cursor); | |||||
return array( | |||||
'id' => $commit->getID(), | |||||
'epoch' => $commit->getEpoch(), | |||||
); | |||||
} | |||||
public function getBuiltinOrders() { | |||||
$parent = parent::getBuiltinOrders(); | |||||
// Rename the default ID-based orders. | |||||
$parent['importnew'] = array( | |||||
'name' => pht('Import Date (Newest First)'), | |||||
) + $parent['newest']; | |||||
$parent['importold'] = array( | |||||
'name' => pht('Import Date (Oldest First)'), | |||||
) + $parent['oldest']; | |||||
return array( | |||||
'newest' => array( | |||||
'vector' => array('epoch', 'id'), | |||||
'name' => pht('Commit Date (Newest First)'), | |||||
), | |||||
'oldest' => array( | |||||
'vector' => array('-epoch', '-id'), | |||||
'name' => pht('Commit Date (Oldest First)'), | |||||
), | |||||
) + $parent; | |||||
} | |||||
} | } |