diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -596,14 +596,17 @@ private function buildSelectStatement(AphrontDatabaseConnection $conn_r) { $table = new DifferentialRevision(); - $select = qsprintf( + $select = $this->buildSelectClause($conn_r); + + $from = qsprintf( $conn_r, - 'SELECT r.* FROM %T r', + 'FROM %T r', $table->getTableName()); $joins = $this->buildJoinsClause($conn_r); $where = $this->buildWhereClause($conn_r); $group_by = $this->buildGroupByClause($conn_r); + $having = $this->buildHavingClause($conn_r); $this->buildingGlobalOrder = false; $order_by = $this->buildOrderClause($conn_r); @@ -612,11 +615,13 @@ return qsprintf( $conn_r, - '(%Q %Q %Q %Q %Q %Q)', + '(%Q %Q %Q %Q %Q %Q %Q %Q)', $select, + $from, $joins, $where, $group_by, + $having, $order_by, $limit); } @@ -684,9 +689,9 @@ DifferentialRevision::TABLE_COMMIT); } - $joins = implode(' ', $joins); + $joins[] = $this->buildJoinClauseParts($conn_r); - return $joins; + return $this->formatJoinClause($joins); } @@ -839,7 +844,7 @@ "Unknown revision status filter constant '{$this->status}'!"); } - $where[] = $this->buildPagingClause($conn_r); + $where[] = $this->buildWhereClauseParts($conn_r); return $this->formatWhereClause($where); } @@ -1161,4 +1166,8 @@ return 'PhabricatorDifferentialApplication'; } + protected function getPrimaryTableAlias() { + return 'r'; + } + } diff --git a/src/applications/differential/query/DifferentialRevisionSearchEngine.php b/src/applications/differential/query/DifferentialRevisionSearchEngine.php --- a/src/applications/differential/query/DifferentialRevisionSearchEngine.php +++ b/src/applications/differential/query/DifferentialRevisionSearchEngine.php @@ -46,6 +46,11 @@ 'repositoryPHIDs', $request->getArr('repositories')); + // TODO: Implement "readProjectsFromRequest(...)" to improve this. + $saved->setParameter( + 'projects', + $this->readListFromRequest($request, 'projects')); + $saved->setParameter( 'draft', $request->getBool('draft')); @@ -76,6 +81,8 @@ $query->withResponsibleUsers($responsible_phids); } + $this->setQueryProjects($query, $saved); + $author_phids = $saved->getParameter('authorPHIDs', array()); if ($author_phids) { $query->withAuthors($author_phids); @@ -127,6 +134,7 @@ $subscriber_phids = $saved->getParameter('subscriberPHIDs', array()); $repository_phids = $saved->getParameter('repositoryPHIDs', array()); $only_draft = $saved->getParameter('draft', false); + $projects = $saved->getParameter('projects', array()); $form ->appendControl( @@ -159,6 +167,12 @@ ->setName('repositories') ->setDatasource(new DiffusionRepositoryDatasource()) ->setValue($repository_phids)) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setLabel(pht('Projects')) + ->setName('projects') + ->setDatasource(new PhabricatorProjectLogicalDatasource()) + ->setValue($projects)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Status')) diff --git a/src/applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php b/src/applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php --- a/src/applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php @@ -17,13 +17,13 @@ ); } - public function evaluateTokens(array $tokens) { - $results = parent::evaluateTokens($tokens); - + protected function didEvaluateTokens(array $results) { foreach ($results as $key => $result) { - $results[$key] = new PhabricatorQueryConstraint( - PhabricatorQueryConstraint::OPERATOR_AND, - $result); + if (is_string($result)) { + $results[$key] = new PhabricatorQueryConstraint( + PhabricatorQueryConstraint::OPERATOR_AND, + $result); + } } return $results; diff --git a/src/applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php b/src/applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php --- a/src/applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php @@ -75,7 +75,7 @@ $phid); } - return $phids; + return $results; } public function renderFunctionTokens($function, array $argv_list) { diff --git a/src/applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php b/src/applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php --- a/src/applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php @@ -56,7 +56,7 @@ return $results; } - public function renderViewerProjectsFunctionTokens( + public function renderFunctionTokens( $function, array $argv_list) { diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -219,6 +219,23 @@ return $named_queries; } + protected function setQueryProjects( + PhabricatorCursorPagedPolicyAwareQuery $query, + PhabricatorSavedQuery $saved) { + + $datasource = id(new PhabricatorProjectLogicalDatasource()) + ->setViewer($this->requireViewer()); + + $projects = $saved->getParameter('projects', array()); + $constraints = $datasource->evaluateTokens($projects); + + if ($constraints) { + $query->withEdgeLogicConstraints( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + $constraints); + } + } + /* -( Applications )------------------------------------------------------- */ diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php @@ -117,6 +117,12 @@ return $this->usable; } + protected function didEvaluateTokens(array $results) { + foreach ($this->getUsableDatasources() as $source) { + $results = $source->didEvaluateTokens($results); + } + return $results; + } protected function canEvaluateFunction($function) { foreach ($this->getUsableDatasources() as $source) { diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php @@ -324,6 +324,16 @@ } } + $results = $this->didEvaluateTokens($results); + + return $results; + } + + + /** + * @task functions + */ + protected function didEvaluateTokens(array $results) { return $results; } diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -21,6 +21,7 @@ private $internalPaging; private $orderVector; private $builtinOrder; + private $edgeLogicConstraints = array(); protected function getPageCursors(array $page) { return array( @@ -1315,6 +1316,10 @@ $joins = array(); foreach ($this->edgeLogicConstraints as $type => $constraints) { + + $op_null = PhabricatorQueryConstraint::OPERATOR_NULL; + $has_null = isset($constraints[$op_null]); + foreach ($constraints as $operator => $list) { $alias = $this->getEdgeLogicTableAlias($operator, $type); switch ($operator) { @@ -1334,10 +1339,20 @@ break; case PhabricatorQueryConstraint::OPERATOR_AND: case PhabricatorQueryConstraint::OPERATOR_OR: + // If we're including results with no matches, we have to degrade + // this to a LEFT join. We'll use WHERE to select matching rows + // later. + if ($has_null) { + $join_type = 'LEFT'; + } else { + $join_type = ''; + } + $joins[] = qsprintf( $conn, - 'JOIN %T %T ON %Q = %T.src AND %T.type = %d + '%Q JOIN %T %T ON %Q = %T.src AND %T.type = %d AND %T.dst IN (%Ls)', + $join_type, $edge_table, $alias, $phid_column, @@ -1377,6 +1392,9 @@ $full = array(); $null = array(); + $op_null = PhabricatorQueryConstraint::OPERATOR_NULL; + $has_null = isset($constraints[$op_null]); + foreach ($constraints as $operator => $list) { $alias = $this->getEdgeLogicTableAlias($operator, $type); switch ($operator) { @@ -1388,6 +1406,12 @@ break; case PhabricatorQueryConstraint::OPERATOR_AND: case PhabricatorQueryConstraint::OPERATOR_OR: + if ($has_null) { + $full[] = qsprintf( + $conn, + '%T.dst IS NOT NULL', + $alias); + } break; case PhabricatorQueryConstraint::OPERATOR_NULL: $null[] = qsprintf(