Differential D14910 Diff 36031 src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
Changeset View
Changeset View
Standalone View
Standalone View
src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
Show First 20 Lines • Show All 1,510 Lines • ▼ Show 20 Lines | /* -( Edge Logic )--------------------------------------------------------- */ | ||||
* @task edgelogic | * @task edgelogic | ||||
*/ | */ | ||||
public function withEdgeLogicConstraints($edge_type, array $constraints) { | public function withEdgeLogicConstraints($edge_type, array $constraints) { | ||||
assert_instances_of($constraints, 'PhabricatorQueryConstraint'); | assert_instances_of($constraints, 'PhabricatorQueryConstraint'); | ||||
$constraints = mgroup($constraints, 'getOperator'); | $constraints = mgroup($constraints, 'getOperator'); | ||||
foreach ($constraints as $operator => $list) { | foreach ($constraints as $operator => $list) { | ||||
foreach ($list as $item) { | foreach ($list as $item) { | ||||
$value = $item->getValue(); | $this->edgeLogicConstraints[$edge_type][$operator][] = $item; | ||||
$this->edgeLogicConstraints[$edge_type][$operator][$value] = $item; | |||||
} | } | ||||
} | } | ||||
$this->edgeLogicConstraintsAreValid = false; | $this->edgeLogicConstraintsAreValid = false; | ||||
return $this; | return $this; | ||||
} | } | ||||
Show All 14 Lines | foreach ($this->edgeLogicConstraints as $type => $constraints) { | ||||
if (count($list) > 1) { | if (count($list) > 1) { | ||||
$select[] = qsprintf( | $select[] = qsprintf( | ||||
$conn, | $conn, | ||||
'COUNT(DISTINCT(%T.dst)) %T', | 'COUNT(DISTINCT(%T.dst)) %T', | ||||
$alias, | $alias, | ||||
$this->buildEdgeLogicTableAliasCount($alias)); | $this->buildEdgeLogicTableAliasCount($alias)); | ||||
} | } | ||||
break; | break; | ||||
case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: | |||||
// This is tricky. We have a query which specifies multiple | |||||
// projects, each of which may have an arbitrarily large number | |||||
// of descendants. | |||||
// Suppose the projects are "Engineering" and "Operations", and | |||||
// "Engineering" has subprojects X, Y and Z. | |||||
// We first use `FIELD(dst, X, Y, Z)` to produce a 0 if a row | |||||
// is not part of Engineering at all, or some number other than | |||||
// 0 if it is. | |||||
// Then we use `IF(..., idx, NULL)` to convert the 0 to a NULL and | |||||
// any other value to an index (say, 1) for the ancestor. | |||||
// We build these up for every ancestor, then use `COALESCE(...)` | |||||
// to select the non-null one, giving us an ancestor which this | |||||
// row is a member of. | |||||
// From there, we use `COUNT(DISTINCT(...))` to make sure that | |||||
// each result row is a member of all ancestors. | |||||
if (count($list) > 1) { | |||||
$idx = 1; | |||||
$parts = array(); | |||||
foreach ($list as $constraint) { | |||||
$parts[] = qsprintf( | |||||
$conn, | |||||
'IF(FIELD(%T.dst, %Ls) != 0, %d, NULL)', | |||||
$alias, | |||||
(array)$constraint->getValue(), | |||||
$idx++); | |||||
} | |||||
$parts = implode(', ', $parts); | |||||
$select[] = qsprintf( | |||||
$conn, | |||||
'COUNT(DISTINCT(COALESCE(%Q))) %T', | |||||
$parts, | |||||
$this->buildEdgeLogicTableAliasAncestor($alias)); | |||||
} | |||||
break; | |||||
default: | default: | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
return $select; | return $select; | ||||
} | } | ||||
Show All 9 Lines | public function buildEdgeLogicJoinClause(AphrontDatabaseConnection $conn) { | ||||
$joins = array(); | $joins = array(); | ||||
foreach ($this->edgeLogicConstraints as $type => $constraints) { | foreach ($this->edgeLogicConstraints as $type => $constraints) { | ||||
$op_null = PhabricatorQueryConstraint::OPERATOR_NULL; | $op_null = PhabricatorQueryConstraint::OPERATOR_NULL; | ||||
$has_null = isset($constraints[$op_null]); | $has_null = isset($constraints[$op_null]); | ||||
foreach ($constraints as $operator => $list) { | foreach ($constraints as $operator => $list) { | ||||
$alias = $this->getEdgeLogicTableAlias($operator, $type); | $alias = $this->getEdgeLogicTableAlias($operator, $type); | ||||
$phids = array(); | |||||
foreach ($list as $constraint) { | |||||
$value = (array)$constraint->getValue(); | |||||
foreach ($value as $v) { | |||||
$phids[$v] = $v; | |||||
} | |||||
} | |||||
$phids = array_keys($phids); | |||||
switch ($operator) { | switch ($operator) { | ||||
case PhabricatorQueryConstraint::OPERATOR_NOT: | case PhabricatorQueryConstraint::OPERATOR_NOT: | ||||
$joins[] = qsprintf( | $joins[] = qsprintf( | ||||
$conn, | $conn, | ||||
'LEFT JOIN %T %T ON %Q = %T.src AND %T.type = %d | 'LEFT JOIN %T %T ON %Q = %T.src AND %T.type = %d | ||||
AND %T.dst IN (%Ls)', | AND %T.dst IN (%Ls)', | ||||
$edge_table, | $edge_table, | ||||
$alias, | $alias, | ||||
$phid_column, | $phid_column, | ||||
$alias, | $alias, | ||||
$alias, | $alias, | ||||
$type, | $type, | ||||
$alias, | $alias, | ||||
mpull($list, 'getValue')); | $phids); | ||||
break; | break; | ||||
case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: | |||||
case PhabricatorQueryConstraint::OPERATOR_AND: | case PhabricatorQueryConstraint::OPERATOR_AND: | ||||
case PhabricatorQueryConstraint::OPERATOR_OR: | case PhabricatorQueryConstraint::OPERATOR_OR: | ||||
// If we're including results with no matches, we have to degrade | // 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 | // this to a LEFT join. We'll use WHERE to select matching rows | ||||
// later. | // later. | ||||
if ($has_null) { | if ($has_null) { | ||||
$join_type = 'LEFT'; | $join_type = 'LEFT'; | ||||
} else { | } else { | ||||
$join_type = ''; | $join_type = ''; | ||||
} | } | ||||
$joins[] = qsprintf( | $joins[] = qsprintf( | ||||
$conn, | $conn, | ||||
'%Q 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)', | AND %T.dst IN (%Ls)', | ||||
$join_type, | $join_type, | ||||
$edge_table, | $edge_table, | ||||
$alias, | $alias, | ||||
$phid_column, | $phid_column, | ||||
$alias, | $alias, | ||||
$alias, | $alias, | ||||
$type, | $type, | ||||
$alias, | $alias, | ||||
mpull($list, 'getValue')); | $phids); | ||||
break; | break; | ||||
case PhabricatorQueryConstraint::OPERATOR_NULL: | case PhabricatorQueryConstraint::OPERATOR_NULL: | ||||
$joins[] = qsprintf( | $joins[] = qsprintf( | ||||
$conn, | $conn, | ||||
'LEFT JOIN %T %T ON %Q = %T.src AND %T.type = %d', | 'LEFT JOIN %T %T ON %Q = %T.src AND %T.type = %d', | ||||
$edge_table, | $edge_table, | ||||
$alias, | $alias, | ||||
$phid_column, | $phid_column, | ||||
▲ Show 20 Lines • Show All 83 Lines • ▼ Show 20 Lines | foreach ($this->edgeLogicConstraints as $type => $constraints) { | ||||
if (count($list) > 1) { | if (count($list) > 1) { | ||||
$having[] = qsprintf( | $having[] = qsprintf( | ||||
$conn, | $conn, | ||||
'%T = %d', | '%T = %d', | ||||
$this->buildEdgeLogicTableAliasCount($alias), | $this->buildEdgeLogicTableAliasCount($alias), | ||||
count($list)); | count($list)); | ||||
} | } | ||||
break; | break; | ||||
case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: | |||||
if (count($list) > 1) { | |||||
$having[] = qsprintf( | |||||
$conn, | |||||
'%T = %d', | |||||
$this->buildEdgeLogicTableAliasAncestor($alias), | |||||
count($list)); | |||||
} | |||||
break; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
return $having; | return $having; | ||||
} | } | ||||
/** | /** | ||||
* @task edgelogic | * @task edgelogic | ||||
*/ | */ | ||||
public function shouldGroupEdgeLogicResultRows() { | public function shouldGroupEdgeLogicResultRows() { | ||||
foreach ($this->edgeLogicConstraints as $type => $constraints) { | foreach ($this->edgeLogicConstraints as $type => $constraints) { | ||||
foreach ($constraints as $operator => $list) { | foreach ($constraints as $operator => $list) { | ||||
switch ($operator) { | switch ($operator) { | ||||
case PhabricatorQueryConstraint::OPERATOR_NOT: | case PhabricatorQueryConstraint::OPERATOR_NOT: | ||||
case PhabricatorQueryConstraint::OPERATOR_AND: | case PhabricatorQueryConstraint::OPERATOR_AND: | ||||
case PhabricatorQueryConstraint::OPERATOR_OR: | case PhabricatorQueryConstraint::OPERATOR_OR: | ||||
case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: | |||||
if (count($list) > 1) { | if (count($list) > 1) { | ||||
return true; | return true; | ||||
} | } | ||||
break; | break; | ||||
case PhabricatorQueryConstraint::OPERATOR_NULL: | case PhabricatorQueryConstraint::OPERATOR_NULL: | ||||
return true; | return true; | ||||
} | } | ||||
} | } | ||||
Show All 13 Lines | /* -( Edge Logic )--------------------------------------------------------- */ | ||||
/** | /** | ||||
* @task edgelogic | * @task edgelogic | ||||
*/ | */ | ||||
private function buildEdgeLogicTableAliasCount($alias) { | private function buildEdgeLogicTableAliasCount($alias) { | ||||
return $alias.'_count'; | return $alias.'_count'; | ||||
} | } | ||||
/** | |||||
* @task edgelogic | |||||
*/ | |||||
private function buildEdgeLogicTableAliasAncestor($alias) { | |||||
return $alias.'_ancestor'; | |||||
} | |||||
/** | /** | ||||
* Select certain edge logic constraint values. | * Select certain edge logic constraint values. | ||||
* | * | ||||
* @task edgelogic | * @task edgelogic | ||||
*/ | */ | ||||
protected function getEdgeLogicValues( | protected function getEdgeLogicValues( | ||||
array $edge_types, | array $edge_types, | ||||
array $operators) { | array $operators) { | ||||
$values = array(); | $values = array(); | ||||
$constraint_lists = $this->edgeLogicConstraints; | $constraint_lists = $this->edgeLogicConstraints; | ||||
if ($edge_types) { | if ($edge_types) { | ||||
$constraint_lists = array_select_keys($constraint_lists, $edge_types); | $constraint_lists = array_select_keys($constraint_lists, $edge_types); | ||||
} | } | ||||
foreach ($constraint_lists as $type => $constraints) { | foreach ($constraint_lists as $type => $constraints) { | ||||
if ($operators) { | if ($operators) { | ||||
$constraints = array_select_keys($constraints, $operators); | $constraints = array_select_keys($constraints, $operators); | ||||
} | } | ||||
foreach ($constraints as $operator => $list) { | foreach ($constraints as $operator => $list) { | ||||
foreach ($list as $constraint) { | foreach ($list as $constraint) { | ||||
$values[] = $constraint->getValue(); | $value = (array)$constraint->getValue(); | ||||
foreach ($value as $v) { | |||||
$values[] = $v; | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
return $values; | return $values; | ||||
} | } | ||||
Show All 14 Lines | private function validateEdgeLogicConstraints() { | ||||
$project_phids = $this->getEdgeLogicValues( | $project_phids = $this->getEdgeLogicValues( | ||||
array( | array( | ||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, | PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, | ||||
), | ), | ||||
array( | array( | ||||
PhabricatorQueryConstraint::OPERATOR_AND, | PhabricatorQueryConstraint::OPERATOR_AND, | ||||
PhabricatorQueryConstraint::OPERATOR_OR, | PhabricatorQueryConstraint::OPERATOR_OR, | ||||
PhabricatorQueryConstraint::OPERATOR_NOT, | PhabricatorQueryConstraint::OPERATOR_NOT, | ||||
PhabricatorQueryConstraint::OPERATOR_ANCESTOR, | |||||
)); | )); | ||||
if ($project_phids) { | if ($project_phids) { | ||||
$projects = id(new PhabricatorProjectQuery()) | $projects = id(new PhabricatorProjectQuery()) | ||||
->setViewer($this->getViewer()) | ->setViewer($this->getViewer()) | ||||
->setParentQuery($this) | ->setParentQuery($this) | ||||
->withPHIDs($project_phids) | ->withPHIDs($project_phids) | ||||
->execute(); | ->execute(); | ||||
$projects = mpull($projects, null, 'getPHID'); | $projects = mpull($projects, null, 'getPHID'); | ||||
▲ Show 20 Lines • Show All 196 Lines • Show Last 20 Lines |