Differential D14910 Diff 36035 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 | |||||