diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2324,6 +2324,7 @@ 'PhabricatorProjectsPolicyRule' => 'applications/policy/rule/PhabricatorProjectsPolicyRule.php', 'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php', 'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php', + 'PhabricatorQueryConstraint' => 'infrastructure/query/constraint/PhabricatorQueryConstraint.php', 'PhabricatorQueryOrderItem' => 'infrastructure/query/order/PhabricatorQueryOrderItem.php', 'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php', 'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php', @@ -5702,6 +5703,7 @@ 'PhabricatorProjectWatchController' => 'PhabricatorProjectController', 'PhabricatorProjectsPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck', + 'PhabricatorQueryConstraint' => 'Phobject', 'PhabricatorQueryOrderItem' => 'Phobject', 'PhabricatorQueryOrderTestCase' => 'PhabricatorTestCase', 'PhabricatorQueryOrderVector' => array( diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -121,7 +121,7 @@ return $conpherences; } - private function buildGroupClause($conn_r) { + protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { if ($this->participantPHIDs !== null) { return 'GROUP BY conpherence_thread.id'; } else { diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php --- a/src/applications/diffusion/query/DiffusionCommitQuery.php +++ b/src/applications/diffusion/query/DiffusionCommitQuery.php @@ -542,7 +542,7 @@ } } - private function buildGroupClause(AphrontDatabaseConnection $conn_r) { + protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { $should_group = $this->shouldJoinAudits(); // TODO: Currently, the audit table is missing a unique key, so we may diff --git a/src/applications/feed/query/PhabricatorFeedQuery.php b/src/applications/feed/query/PhabricatorFeedQuery.php --- a/src/applications/feed/query/PhabricatorFeedQuery.php +++ b/src/applications/feed/query/PhabricatorFeedQuery.php @@ -82,7 +82,7 @@ return $this->formatWhereClause($where); } - private function buildGroupClause(AphrontDatabaseConnection $conn_r) { + protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { if ($this->filterPHIDs) { return qsprintf($conn_r, 'GROUP BY ref.chronologicalKey'); } else { diff --git a/src/applications/legalpad/query/LegalpadDocumentQuery.php b/src/applications/legalpad/query/LegalpadDocumentQuery.php --- a/src/applications/legalpad/query/LegalpadDocumentQuery.php +++ b/src/applications/legalpad/query/LegalpadDocumentQuery.php @@ -157,7 +157,7 @@ return implode(' ', $joins); } - private function buildGroupClause(AphrontDatabaseConnection $conn_r) { + protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { if ($this->contributorPHIDs || $this->signerPHIDs) { return 'GROUP BY d.id'; } else { diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -837,7 +837,7 @@ return implode(' ', $joins); } - private function buildGroupClause(AphrontDatabaseConnection $conn_r) { + protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { $joined_multiple_rows = (count($this->projectPHIDs) > 1) || (count($this->anyProjectPHIDs) > 1) || $this->shouldJoinBlockingTasks() || diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -186,7 +186,7 @@ return $users; } - private function buildGroupClause(AphrontDatabaseConnection $conn) { + protected function buildGroupClause(AphrontDatabaseConnection $conn) { if ($this->nameTokens) { return qsprintf( $conn, diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -328,7 +328,7 @@ return $this->formatWhereClause($where); } - private function buildGroupClause($conn_r) { + protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { if ($this->memberPHIDs || $this->nameTokens) { return 'GROUP BY p.id'; } else { diff --git a/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php b/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php new file mode 100644 --- /dev/null +++ b/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php @@ -0,0 +1,36 @@ +operator = $operator; + $this->value = $value; + } + + public function setOperator($operator) { + $this->operator = $operator; + return $this; + } + + public function getOperator() { + return $this->operator; + } + + public function setValue($value) { + $this->value = $value; + return $this; + } + + public function getValue() { + return $this->value; + } + +} 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 @@ -9,6 +9,7 @@ * @task customfield Integration with CustomField * @task paging Paging * @task order Result Ordering + * @task edgelogic Working with Edge Logic */ abstract class PhabricatorCursorPagedPolicyAwareQuery extends PhabricatorPolicyAwareQuery { @@ -202,6 +203,8 @@ $select[] = '*'; } + $select[] = $this->buildEdgeLogicSelectClause($conn); + return $select; } @@ -220,6 +223,7 @@ */ protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { $joins = array(); + $joins[] = $this->buildEdgeLogicJoinClause($conn); return $joins; } @@ -239,6 +243,7 @@ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = array(); $where[] = $this->buildPagingClause($conn); + $where[] = $this->buildEdgeLogicWhereClause($conn); return $where; } @@ -257,10 +262,43 @@ */ protected function buildHavingClauseParts(AphrontDatabaseConnection $conn) { $having = array(); + $having[] = $this->buildEdgeLogicHavingClause($conn); return $having; } + /** + * @task clauses + */ + protected function buildGroupClause(AphrontDatabaseConnection $conn) { + if (!$this->shouldGroupQueryResultRows()) { + return ''; + } + + return qsprintf( + $conn, + 'GROUP BY %Q', + $this->getApplicationSearchObjectPHIDColumn()); + } + + + /** + * @task clauses + */ + protected function shouldGroupQueryResultRows() { + if ($this->shouldGroupEdgeLogicResultRows()) { + return true; + } + + if ($this->getApplicationSearchMayJoinMultipleRows()) { + return true; + } + + return false; + } + + + /* -( Paging )------------------------------------------------------------- */ @@ -1218,4 +1256,232 @@ } +/* -( Edge Logic )--------------------------------------------------------- */ + + + /** + * @task edgelogic + */ + public function withEdgeLogicConstraints($edge_type, array $constraints) { + assert_instances_of($constraints, 'PhabricatorQueryConstraint'); + + $constraints = mgroup($constraints, 'getOperator'); + foreach ($constraints as $operator => $list) { + foreach ($list as $item) { + $value = $item->getValue(); + $this->edgeLogicConstraints[$edge_type][$operator][$value] = $item; + } + } + + return $this; + } + + + /** + * @task edgelogic + */ + public function buildEdgeLogicSelectClause(AphrontDatabaseConnection $conn) { + $select = array(); + + foreach ($this->edgeLogicConstraints as $type => $constraints) { + foreach ($constraints as $operator => $list) { + $alias = $this->getEdgeLogicTableAlias($operator, $type); + switch ($operator) { + case PhabricatorQueryConstraint::OPERATOR_AND: + if (count($list) > 1) { + $select[] = qsprintf( + $conn, + 'COUNT(DISTINCT(%T.dst)) %T', + $alias, + $this->buildEdgeLogicTableAliasCount($alias)); + } + break; + default: + break; + } + } + } + + return $select; + } + + + /** + * @task edgelogic + */ + public function buildEdgeLogicJoinClause(AphrontDatabaseConnection $conn) { + $edge_table = PhabricatorEdgeConfig::TABLE_NAME_EDGE; + $phid_column = $this->getApplicationSearchObjectPHIDColumn(); + + $joins = array(); + foreach ($this->edgeLogicConstraints as $type => $constraints) { + foreach ($constraints as $operator => $list) { + $alias = $this->getEdgeLogicTableAlias($operator, $type); + switch ($operator) { + case PhabricatorQueryConstraint::OPERATOR_NOT: + $joins[] = qsprintf( + $conn, + 'LEFT JOIN %T %T ON %Q = %T.src AND %T.type = %d + AND %T.dst IN (%Ls)', + $edge_table, + $alias, + $phid_column, + $alias, + $alias, + $type, + $alias, + mpull($list, 'getValue')); + break; + case PhabricatorQueryConstraint::OPERATOR_AND: + case PhabricatorQueryConstraint::OPERATOR_OR: + $joins[] = qsprintf( + $conn, + 'JOIN %T %T ON %Q = %T.src AND %T.type = %d + AND %T.dst IN (%Ls)', + $edge_table, + $alias, + $phid_column, + $alias, + $alias, + $type, + $alias, + mpull($list, 'getValue')); + break; + case PhabricatorQueryConstraint::OPERATOR_NULL: + $joins[] = qsprintf( + $conn, + 'LEFT JOIN %T %T ON %Q = %T.src AND %T.type = %d', + $edge_table, + $alias, + $phid_column, + $alias, + $alias, + $type); + break; + } + } + } + + return $joins; + } + + + /** + * @task edgelogic + */ + public function buildEdgeLogicWhereClause(AphrontDatabaseConnection $conn) { + $where = array(); + + foreach ($this->edgeLogicConstraints as $type => $constraints) { + + $full = array(); + $null = array(); + + foreach ($constraints as $operator => $list) { + $alias = $this->getEdgeLogicTableAlias($operator, $type); + switch ($operator) { + case PhabricatorQueryConstraint::OPERATOR_NOT: + $full[] = qsprintf( + $conn, + '%T.dst IS NULL', + $alias); + break; + case PhabricatorQueryConstraint::OPERATOR_AND: + case PhabricatorQueryConstraint::OPERATOR_OR: + break; + case PhabricatorQueryConstraint::OPERATOR_NULL: + $null[] = qsprintf( + $conn, + '%T.dst IS NULL', + $alias); + break; + } + } + + if ($full && $null) { + $full = $this->formatWhereSubclause($full); + $null = $this->formatWhereSubclause($null); + $where[] = qsprintf($conn, '(%Q OR %Q)', $full, $null); + } else if ($full) { + foreach ($full as $condition) { + $where[] = $condition; + } + } else if ($null) { + foreach ($null as $condition) { + $where[] = $condition; + } + } + } + + return $where; + } + + + /** + * @task edgelogic + */ + public function buildEdgeLogicHavingClause(AphrontDatabaseConnection $conn) { + $having = array(); + + foreach ($this->edgeLogicConstraints as $type => $constraints) { + foreach ($constraints as $operator => $list) { + $alias = $this->getEdgeLogicTableAlias($operator, $type); + switch ($operator) { + case PhabricatorQueryConstraint::OPERATOR_AND: + if (count($list) > 1) { + $having[] = qsprintf( + $conn, + '%T = %d', + $this->buildEdgeLogicTableAliasCount($alias), + count($list)); + } + break; + } + } + } + + return $having; + } + + + /** + * @task edgelogic + */ + public function shouldGroupEdgeLogicResultRows() { + foreach ($this->edgeLogicConstraints as $type => $constraints) { + foreach ($constraints as $operator => $list) { + switch ($operator) { + case PhabricatorQueryConstraint::OPERATOR_NOT: + case PhabricatorQueryConstraint::OPERATOR_AND: + case PhabricatorQueryConstraint::OPERATOR_OR: + if (count($list) > 1) { + return true; + } + break; + case PhabricatorQueryConstraint::OPERATOR_NULL: + return true; + } + } + } + + return false; + } + + + /** + * @task edgelogic + */ + private function getEdgeLogicTableAlias($operator, $type) { + return 'edgelogic_'.$operator.'_'.$type; + } + + + /** + * @task edgelogic + */ + private function buildEdgeLogicTableAliasCount($alias) { + return $alias.'_count'; + } + + }