mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-23 22:10:55 +01:00
Add "Edge Logic" support to PolicyAwareQuery
Summary: Ref T4100. Ref T5595. This allows PolicyAwareQuery to write all the logic for AND, OR, NOT, and NULL (i.e., "not in any projects") queries against any edge type. It accepts an edge type and a list of constraints (which are basically just operator-value pairs, like `<NOT, PHID-X-Y>`, meaning the results must not have an edge connecting them to `PHID-X-Y`). This doesn't actually do anything yet; see future diffs. Test Plan: `arc unit --everything` Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T4100, T5595 Differential Revision: https://secure.phabricator.com/D12455
This commit is contained in:
parent
55e49d7e31
commit
e27c0b416d
10 changed files with 311 additions and 7 deletions
|
@ -2324,6 +2324,7 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectWatchController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectsPolicyRule' => 'PhabricatorPolicyRule',
|
||||
'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck',
|
||||
'PhabricatorQueryConstraint' => 'Phobject',
|
||||
'PhabricatorQueryOrderItem' => 'Phobject',
|
||||
'PhabricatorQueryOrderTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorQueryOrderVector' => array(
|
||||
|
|
|
@ -121,7 +121,7 @@ final class ConpherenceThreadQuery
|
|||
return $conpherences;
|
||||
}
|
||||
|
||||
private function buildGroupClause($conn_r) {
|
||||
protected function buildGroupClause(AphrontDatabaseConnection $conn_r) {
|
||||
if ($this->participantPHIDs !== null) {
|
||||
return 'GROUP BY conpherence_thread.id';
|
||||
} else {
|
||||
|
|
|
@ -542,7 +542,7 @@ final class DiffusionCommitQuery
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -82,7 +82,7 @@ final class PhabricatorFeedQuery
|
|||
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 {
|
||||
|
|
|
@ -157,7 +157,7 @@ final class LegalpadDocumentQuery
|
|||
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 {
|
||||
|
|
|
@ -837,7 +837,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
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() ||
|
||||
|
|
|
@ -186,7 +186,7 @@ final class PhabricatorPeopleQuery
|
|||
return $users;
|
||||
}
|
||||
|
||||
private function buildGroupClause(AphrontDatabaseConnection $conn) {
|
||||
protected function buildGroupClause(AphrontDatabaseConnection $conn) {
|
||||
if ($this->nameTokens) {
|
||||
return qsprintf(
|
||||
$conn,
|
||||
|
|
|
@ -328,7 +328,7 @@ final class PhabricatorProjectQuery
|
|||
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 {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorQueryConstraint extends Phobject {
|
||||
|
||||
const OPERATOR_AND = 'and';
|
||||
const OPERATOR_OR = 'or';
|
||||
const OPERATOR_NOT = 'not';
|
||||
const OPERATOR_NULL = 'null';
|
||||
|
||||
private $operator;
|
||||
private $value;
|
||||
|
||||
public function __construct($operator, $value) {
|
||||
$this->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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
|||
$select[] = '*';
|
||||
}
|
||||
|
||||
$select[] = $this->buildEdgeLogicSelectClause($conn);
|
||||
|
||||
return $select;
|
||||
}
|
||||
|
||||
|
@ -220,6 +223,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
|||
*/
|
||||
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$joins = array();
|
||||
$joins[] = $this->buildEdgeLogicJoinClause($conn);
|
||||
return $joins;
|
||||
}
|
||||
|
||||
|
@ -239,6 +243,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
|||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = array();
|
||||
$where[] = $this->buildPagingClause($conn);
|
||||
$where[] = $this->buildEdgeLogicWhereClause($conn);
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
@ -257,10 +262,43 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
|||
*/
|
||||
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 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
|||
}
|
||||
|
||||
|
||||
/* -( 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';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue